/*!
 * # Semantic UI 2.4.1 - Dropdown
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ($, window, document, undefined) {

    'use strict';

    window = (typeof window != 'undefined' && window.Math == Math)
        ? window
        : (typeof self != 'undefined' && self.Math == Math)
            ? self
            : Function('return this')()
    ;

    $.fn.dropdown = function (parameters) {
        var
            $allModules = $(this),
            $document = $(document),

            moduleSelector = $allModules.selector || '',

            hasTouch = ('ontouchstart' in document.documentElement),
            time = new Date().getTime(),
            performance = [],

            query = arguments[0],
            methodInvoked = (typeof query == 'string'),
            queryArguments = [].slice.call(arguments, 1),
            returnedValue
        ;

        $allModules
            .each(function (elementIndex) {
                var
                    settings = ($.isPlainObject(parameters))
                        ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
                        : $.extend({}, $.fn.dropdown.settings),

                    className = settings.className,
                    message = settings.message,
                    fields = settings.fields,
                    keys = settings.keys,
                    metadata = settings.metadata,
                    namespace = settings.namespace,
                    regExp = settings.regExp,
                    selector = settings.selector,
                    error = settings.error,
                    templates = settings.templates,

                    eventNamespace = '.' + namespace,
                    moduleNamespace = 'module-' + namespace,

                    $module = $(this),
                    $context = $(settings.context),
                    $text = $module.find(selector.text),
                    $search = $module.find(selector.search),
                    $sizer = $module.find(selector.sizer),
                    $input = $module.find(selector.input),
                    $icon = $module.find(selector.icon),

                    $combo = ($module.prev().find(selector.text).length > 0)
                        ? $module.prev().find(selector.text)
                        : $module.prev(),

                    $menu = $module.children(selector.menu),
                    $item = $menu.find(selector.item),

                    activated = false,
                    itemActivated = false,
                    internalChange = false,
                    element = this,
                    instance = $module.data(moduleNamespace),

                    initialLoad,
                    pageLostFocus,
                    willRefocus,
                    elementNamespace,
                    id,
                    selectObserver,
                    menuObserver,
                    module
                ;

                module = {

                    initialize: function () {
                        module.debug('Initializing dropdown', settings);

                        if (module.is.alreadySetup()) {
                            module.setup.reference();
                        }
                        else {

                            module.setup.layout();

                            if (settings.values) {
                                module.change.values(settings.values);
                            }

                            module.refreshData();

                            module.save.defaults();
                            module.restore.selected();

                            module.create.id();
                            module.bind.events();

                            module.observeChanges();
                            module.instantiate();
                        }

                    },

                    instantiate: function () {
                        module.verbose('Storing instance of dropdown', module);
                        instance = module;
                        $module
                            .data(moduleNamespace, module)
                        ;
                    },

                    destroy: function () {
                        module.verbose('Destroying previous dropdown', $module);
                        module.remove.tabbable();
                        $module
                            .off(eventNamespace)
                            .removeData(moduleNamespace)
                        ;
                        $menu
                            .off(eventNamespace)
                        ;
                        $document
                            .off(elementNamespace)
                        ;
                        module.disconnect.menuObserver();
                        module.disconnect.selectObserver();
                    },

                    observeChanges: function () {
                        if ('MutationObserver' in window) {
                            selectObserver = new MutationObserver(module.event.select.mutation);
                            menuObserver = new MutationObserver(module.event.menu.mutation);
                            module.debug('Setting up mutation observer', selectObserver, menuObserver);
                            module.observe.select();
                            module.observe.menu();
                        }
                    },

                    disconnect: {
                        menuObserver: function () {
                            if (menuObserver) {
                                menuObserver.disconnect();
                            }
                        },
                        selectObserver: function () {
                            if (selectObserver) {
                                selectObserver.disconnect();
                            }
                        }
                    },
                    observe: {
                        select: function () {
                            if (module.has.input()) {
                                selectObserver.observe($module[0], {
                                    childList: true,
                                    subtree: true
                                });
                            }
                        },
                        menu: function () {
                            if (module.has.menu()) {
                                menuObserver.observe($menu[0], {
                                    childList: true,
                                    subtree: true
                                });
                            }
                        }
                    },

                    create: {
                        id: function () {
                            id = (Math.random().toString(16) + '000000000').substr(2, 8);
                            elementNamespace = '.' + id;
                            module.verbose('Creating unique id for element', id);
                        },
                        userChoice: function (values) {
                            var
                                $userChoices,
                                $userChoice,
                                isUserValue,
                                html
                            ;
                            values = values || module.get.userValues();
                            if (!values) {
                                return false;
                            }
                            values = $.isArray(values)
                                ? values
                                : [values]
                            ;
                            $.each(values, function (index, value) {
                                if (module.get.item(value) === false) {
                                    html = settings.templates.addition(module.add.variables(message.addResult, value));
                                    $userChoice = $('<div />')
                                        .html(html)
                                        .attr('data-' + metadata.value, value)
                                        .attr('data-' + metadata.text, value)
                                        .addClass(className.addition)
                                        .addClass(className.item)
                                    ;
                                    if (settings.hideAdditions) {
                                        $userChoice.addClass(className.hidden);
                                    }
                                    $userChoices = ($userChoices === undefined)
                                        ? $userChoice
                                        : $userChoices.add($userChoice)
                                    ;
                                    module.verbose('Creating user choices for value', value, $userChoice);
                                }
                            });
                            return $userChoices;
                        },
                        userLabels: function (value) {
                            var
                                userValues = module.get.userValues()
                            ;
                            if (userValues) {
                                module.debug('Adding user labels', userValues);
                                $.each(userValues, function (index, value) {
                                    module.verbose('Adding custom user value');
                                    module.add.label(value, value);
                                });
                            }
                        },
                        menu: function () {
                            $menu = $('<div />')
                                .addClass(className.menu)
                                .appendTo($module)
                            ;
                        },
                        sizer: function () {
                            $sizer = $('<span />')
                                .addClass(className.sizer)
                                .insertAfter($search)
                            ;
                        }
                    },

                    search: function (query) {
                        query = (query !== undefined)
                            ? query
                            : module.get.query()
                        ;
                        module.verbose('Searching for query', query);
                        if (module.has.minCharacters(query)) {
                            module.filter(query);
                        }
                        else {
                            module.hide();
                        }
                    },

                    select: {
                        firstUnfiltered: function () {
                            module.verbose('Selecting first non-filtered element');
                            module.remove.selectedItem();
                            $item
                                .not(selector.unselectable)
                                .not(selector.addition + selector.hidden)
                                .eq(0)
                                .addClass(className.selected)
                            ;
                        },
                        nextAvailable: function ($selected) {
                            $selected = $selected.eq(0);
                            var
                                $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
                                $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
                                hasNext = ($nextAvailable.length > 0)
                            ;
                            if (hasNext) {
                                module.verbose('Moving selection to', $nextAvailable);
                                $nextAvailable.addClass(className.selected);
                            }
                            else {
                                module.verbose('Moving selection to', $prevAvailable);
                                $prevAvailable.addClass(className.selected);
                            }
                        }
                    },

                    setup: {
                        api: function () {
                            var
                                apiSettings = {
                                    debug: settings.debug,
                                    urlData: {
                                        value: module.get.value(),
                                        query: module.get.query()
                                    },
                                    on: false
                                }
                            ;
                            module.verbose('First request, initializing API');
                            $module
                                .api(apiSettings)
                            ;
                        },
                        layout: function () {
                            if ($module.is('select')) {
                                module.setup.select();
                                module.setup.returnedObject();
                            }
                            if (!module.has.menu()) {
                                module.create.menu();
                            }
                            if (module.is.search() && !module.has.search()) {
                                module.verbose('Adding search input');
                                $search = $('<input />')
                                    .addClass(className.search)
                                    .prop('autocomplete', 'off')
                                    .insertBefore($text)
                                ;
                            }
                            if (module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
                                module.create.sizer();
                            }
                            if (settings.allowTab) {
                                module.set.tabbable();
                            }
                        },
                        select: function () {
                            var
                                selectValues = module.get.selectValues()
                            ;
                            module.debug('Dropdown initialized on a select', selectValues);
                            if ($module.is('select')) {
                                $input = $module;
                            }
                            // see if select is placed correctly already
                            if ($input.parent(selector.dropdown).length > 0) {
                                module.debug('UI dropdown already exists. Creating dropdown menu only');
                                $module = $input.closest(selector.dropdown);
                                if (!module.has.menu()) {
                                    module.create.menu();
                                }
                                $menu = $module.children(selector.menu);
                                module.setup.menu(selectValues);
                            }
                            else {
                                module.debug('Creating entire dropdown from select');
                                $module = $('<div />')
                                    .attr('class', $input.attr('class'))
                                    .addClass(className.selection)
                                    .addClass(className.dropdown)
                                    .html(templates.dropdown(selectValues))
                                    .insertBefore($input)
                                ;
                                if ($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
                                    module.error(error.missingMultiple);
                                    $input.prop('multiple', true);
                                }
                                if ($input.is('[multiple]')) {
                                    module.set.multiple();
                                }
                                if ($input.prop('disabled')) {
                                    module.debug('Disabling dropdown');
                                    $module.addClass(className.disabled);
                                }
                                $input
                                    .removeAttr('class')
                                    .detach()
                                    .prependTo($module)
                                ;
                            }
                            module.refresh();
                        },
                        menu: function (values) {
                            $menu.html(templates.menu(values, fields));
                            $item = $menu.find(selector.item);
                        },
                        reference: function () {
                            module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
                            // replace module reference
                            $module = $module.parent(selector.dropdown);
                            instance = $module.data(moduleNamespace);
                            element = $module.get(0);
                            module.refresh();
                            module.setup.returnedObject();
                        },
                        returnedObject: function () {
                            var
                                $firstModules = $allModules.slice(0, elementIndex),
                                $lastModules = $allModules.slice(elementIndex + 1)
                            ;
                            // adjust all modules to use correct reference
                            $allModules = $firstModules.add($module).add($lastModules);
                        }
                    },

                    refresh: function () {
                        module.refreshSelectors();
                        module.refreshData();
                    },

                    refreshItems: function () {
                        $item = $menu.find(selector.item);
                    },

                    refreshSelectors: function () {
                        module.verbose('Refreshing selector cache');
                        $text = $module.find(selector.text);
                        $search = $module.find(selector.search);
                        $input = $module.find(selector.input);
                        $icon = $module.find(selector.icon);
                        $combo = ($module.prev().find(selector.text).length > 0)
                            ? $module.prev().find(selector.text)
                            : $module.prev()
                        ;
                        $menu = $module.children(selector.menu);
                        $item = $menu.find(selector.item);
                    },

                    refreshData: function () {
                        module.verbose('Refreshing cached metadata');
                        $item
                            .removeData(metadata.text)
                            .removeData(metadata.value)
                        ;
                    },

                    clearData: function () {
                        module.verbose('Clearing metadata');
                        $item
                            .removeData(metadata.text)
                            .removeData(metadata.value)
                        ;
                        $module
                            .removeData(metadata.defaultText)
                            .removeData(metadata.defaultValue)
                            .removeData(metadata.placeholderText)
                        ;
                    },

                    toggle: function () {
                        module.verbose('Toggling menu visibility');
                        if (!module.is.active()) {
                            module.show();
                        }
                        else {
                            module.hide();
                        }
                    },

                    show: function (callback) {
                        callback = $.isFunction(callback)
                            ? callback
                            : function () {
                            }
                        ;
                        if (!module.can.show() && module.is.remote()) {
                            module.debug('No API results retrieved, searching before show');
                            module.queryRemote(module.get.query(), module.show);
                        }
                        if (module.can.show() && !module.is.active()) {
                            module.debug('Showing dropdown');
                            if (module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered())) {
                                module.remove.message();
                            }
                            if (module.is.allFiltered()) {
                                return true;
                            }
                            if (settings.onShow.call(element) !== false) {
                                module.animate.show(function () {
                                    if (module.can.click()) {
                                        module.bind.intent();
                                    }
                                    if (module.has.menuSearch()) {
                                        module.focusSearch();
                                    }
                                    module.set.visible();
                                    callback.call(element);
                                });
                            }
                        }
                    },

                    hide: function (callback) {
                        callback = $.isFunction(callback)
                            ? callback
                            : function () {
                            }
                        ;
                        if (module.is.active() && !module.is.animatingOutward()) {
                            module.debug('Hiding dropdown');
                            if (settings.onHide.call(element) !== false) {
                                module.animate.hide(function () {
                                    module.remove.visible();
                                    callback.call(element);
                                });
                            }
                        }
                    },

                    hideOthers: function () {
                        module.verbose('Finding other dropdowns to hide');
                        $allModules
                            .not($module)
                            .has(selector.menu + '.' + className.visible)
                            .dropdown('hide')
                        ;
                    },

                    hideMenu: function () {
                        module.verbose('Hiding menu  instantaneously');
                        module.remove.active();
                        module.remove.visible();
                        $menu.transition('hide');
                    },

                    hideSubMenus: function () {
                        var
                            $subMenus = $menu.children(selector.item).find(selector.menu)
                        ;
                        module.verbose('Hiding sub menus', $subMenus);
                        $subMenus.transition('hide');
                    },

                    bind: {
                        events: function () {
                            if (hasTouch) {
                                module.bind.touchEvents();
                            }
                            module.bind.keyboardEvents();
                            module.bind.inputEvents();
                            module.bind.mouseEvents();
                        },
                        touchEvents: function () {
                            module.debug('Touch device detected binding additional touch events');
                            if (module.is.searchSelection()) {
                                // do nothing special yet
                            }
                            else if (module.is.single()) {
                                $module
                                    .on('touchstart' + eventNamespace, module.event.test.toggle)
                                ;
                            }
                            $menu
                                .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
                            ;
                        },
                        keyboardEvents: function () {
                            module.verbose('Binding keyboard events');
                            $module
                                .on('keydown' + eventNamespace, module.event.keydown)
                            ;
                            if (module.has.search()) {
                                $module
                                    .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
                                ;
                            }
                            if (module.is.multiple()) {
                                $document
                                    .on('keydown' + elementNamespace, module.event.document.keydown)
                                ;
                            }
                        },
                        inputEvents: function () {
                            module.verbose('Binding input change events');
                            $module
                                .on('change' + eventNamespace, selector.input, module.event.change)
                            ;
                        },
                        mouseEvents: function () {
                            module.verbose('Binding mouse events');
                            if (module.is.multiple()) {
                                $module
                                    .on('click' + eventNamespace, selector.label, module.event.label.click)
                                    .on('click' + eventNamespace, selector.remove, module.event.remove.click)
                                ;
                            }
                            if (module.is.searchSelection()) {
                                $module
                                    .on('mousedown' + eventNamespace, module.event.mousedown)
                                    .on('mouseup' + eventNamespace, module.event.mouseup)
                                    .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
                                    .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
                                    .on('click' + eventNamespace, selector.icon, module.event.icon.click)
                                    .on('focus' + eventNamespace, selector.search, module.event.search.focus)
                                    .on('click' + eventNamespace, selector.search, module.event.search.focus)
                                    .on('blur' + eventNamespace, selector.search, module.event.search.blur)
                                    .on('click' + eventNamespace, selector.text, module.event.text.focus)
                                ;
                                if (module.is.multiple()) {
                                    $module
                                        .on('click' + eventNamespace, module.event.click)
                                    ;
                                }
                            }
                            else {
                                if (settings.on == 'click') {
                                    $module
                                        .on('click' + eventNamespace, module.event.test.toggle)
                                    ;
                                }
                                else if (settings.on == 'hover') {
                                    $module
                                        .on('mouseenter' + eventNamespace, module.delay.show)
                                        .on('mouseleave' + eventNamespace, module.delay.hide)
                                    ;
                                }
                                else {
                                    $module
                                        .on(settings.on + eventNamespace, module.toggle)
                                    ;
                                }
                                $module
                                    .on('click' + eventNamespace, selector.icon, module.event.icon.click)
                                    .on('mousedown' + eventNamespace, module.event.mousedown)
                                    .on('mouseup' + eventNamespace, module.event.mouseup)
                                    .on('focus' + eventNamespace, module.event.focus)
                                ;
                                if (module.has.menuSearch()) {
                                    $module
                                        .on('blur' + eventNamespace, selector.search, module.event.search.blur)
                                    ;
                                }
                                else {
                                    $module
                                        .on('blur' + eventNamespace, module.event.blur)
                                    ;
                                }
                            }
                            $menu
                                .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
                                .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
                                .on('click' + eventNamespace, selector.item, module.event.item.click)
                            ;
                        },
                        intent: function () {
                            module.verbose('Binding hide intent event to document');
                            if (hasTouch) {
                                $document
                                    .on('touchstart' + elementNamespace, module.event.test.touch)
                                    .on('touchmove' + elementNamespace, module.event.test.touch)
                                ;
                            }
                            $document
                                .on('click' + elementNamespace, module.event.test.hide)
                            ;
                        }
                    },

                    unbind: {
                        intent: function () {
                            module.verbose('Removing hide intent event from document');
                            if (hasTouch) {
                                $document
                                    .off('touchstart' + elementNamespace)
                                    .off('touchmove' + elementNamespace)
                                ;
                            }
                            $document
                                .off('click' + elementNamespace)
                            ;
                        }
                    },

                    filter: function (query) {
                        var
                            searchTerm = (query !== undefined)
                                ? query
                                : module.get.query(),
                            afterFiltered = function () {
                                if (module.is.multiple()) {
                                    module.filterActive();
                                }
                                if (query || (!query && module.get.activeItem().length == 0)) {
                                    module.select.firstUnfiltered();
                                }
                                if (module.has.allResultsFiltered()) {
                                    if (settings.onNoResults.call(element, searchTerm)) {
                                        if (settings.allowAdditions) {
                                            if (settings.hideAdditions) {
                                                module.verbose('User addition with no menu, setting empty style');
                                                module.set.empty();
                                                module.hideMenu();
                                            }
                                        }
                                        else {
                                            module.verbose('All items filtered, showing message', searchTerm);
                                            module.add.message(message.noResults);
                                        }
                                    }
                                    else {
                                        module.verbose('All items filtered, hiding dropdown', searchTerm);
                                        module.hideMenu();
                                    }
                                }
                                else {
                                    module.remove.empty();
                                    module.remove.message();
                                }
                                if (settings.allowAdditions) {
                                    module.add.userSuggestion(query);
                                }
                                if (module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch()) {
                                    module.show();
                                }
                            }
                        ;
                        if (settings.useLabels && module.has.maxSelections()) {
                            return;
                        }
                        if (settings.apiSettings) {
                            if (module.can.useAPI()) {
                                module.queryRemote(searchTerm, function () {
                                    if (settings.filterRemoteData) {
                                        module.filterItems(searchTerm);
                                    }
                                    afterFiltered();
                                });
                            }
                            else {
                                module.error(error.noAPI);
                            }
                        }
                        else {
                            module.filterItems(searchTerm);
                            afterFiltered();
                        }
                    },

                    queryRemote: function (query, callback) {
                        var
                            apiSettings = {
                                errorDuration: false,
                                cache: 'local',
                                throttle: settings.throttle,
                                urlData: {
                                    query: query
                                },
                                onError: function () {
                                    module.add.message(message.serverError);
                                    callback();
                                },
                                onFailure: function () {
                                    module.add.message(message.serverError);
                                    callback();
                                },
                                onSuccess: function (response) {
                                    var
                                        values = response[fields.remoteValues],
                                        hasRemoteValues = ($.isArray(values) && values.length > 0)
                                    ;
                                    if (hasRemoteValues) {
                                        module.remove.message();
                                        module.setup.menu({
                                            values: response[fields.remoteValues]
                                        });
                                    }
                                    else {
                                        module.add.message(message.noResults);
                                    }
                                    callback();
                                }
                            }
                        ;
                        if (!$module.api('get request')) {
                            module.setup.api();
                        }
                        apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
                        $module
                            .api('setting', apiSettings)
                            .api('query')
                        ;
                    },

                    filterItems: function (query) {
                        var
                            searchTerm = (query !== undefined)
                                ? query
                                : module.get.query(),
                            results = null,
                            escapedTerm = module.escape.string(searchTerm),
                            beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
                        ;
                        // avoid loop if we're matching nothing
                        if (module.has.query()) {
                            results = [];

                            module.verbose('Searching for matching values', searchTerm);
                            $item
                                .each(function () {
                                    var
                                        $choice = $(this),
                                        text,
                                        value
                                    ;
                                    if (settings.match == 'both' || settings.match == 'text') {
                                        text = String(module.get.choiceText($choice, false));
                                        if (text.search(beginsWithRegExp) !== -1) {
                                            results.push(this);
                                            return true;
                                        }
                                        else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
                                            results.push(this);
                                            return true;
                                        }
                                        else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
                                            results.push(this);
                                            return true;
                                        }
                                    }
                                    if (settings.match == 'both' || settings.match == 'value') {
                                        value = String(module.get.choiceValue($choice, text));
                                        if (value.search(beginsWithRegExp) !== -1) {
                                            results.push(this);
                                            return true;
                                        }
                                        else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) {
                                            results.push(this);
                                            return true;
                                        }
                                        else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) {
                                            results.push(this);
                                            return true;
                                        }
                                    }
                                })
                            ;
                        }
                        module.debug('Showing only matched items', searchTerm);
                        module.remove.filteredItem();
                        if (results) {
                            $item
                                .not(results)
                                .addClass(className.filtered)
                            ;
                        }
                    },

                    fuzzySearch: function (query, term) {
                        var
                            termLength = term.length,
                            queryLength = query.length
                        ;
                        query = query.toLowerCase();
                        term = term.toLowerCase();
                        if (queryLength > termLength) {
                            return false;
                        }
                        if (queryLength === termLength) {
                            return (query === term);
                        }
                        search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
                            var
                                queryCharacter = query.charCodeAt(characterIndex)
                            ;
                            while (nextCharacterIndex < termLength) {
                                if (term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
                                    continue search;
                                }
                            }
                            return false;
                        }
                        return true;
                    },
                    exactSearch: function (query, term) {
                        query = query.toLowerCase();
                        term = term.toLowerCase();
                        if (term.indexOf(query) > -1) {
                            return true;
                        }
                        return false;
                    },
                    filterActive: function () {
                        if (settings.useLabels) {
                            $item.filter('.' + className.active)
                                .addClass(className.filtered)
                            ;
                        }
                    },

                    focusSearch: function (skipHandler) {
                        if (module.has.search() && !module.is.focusedOnSearch()) {
                            if (skipHandler) {
                                $module.off('focus' + eventNamespace, selector.search);
                                $search.focus();
                                $module.on('focus' + eventNamespace, selector.search, module.event.search.focus);
                            }
                            else {
                                $search.focus();
                            }
                        }
                    },

                    forceSelection: function () {
                        var
                            $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
                            $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
                            $selectedItem = ($currentlySelected.length > 0)
                                ? $currentlySelected
                                : $activeItem,
                            hasSelected = ($selectedItem.length > 0)
                        ;
                        if (hasSelected && !module.is.multiple()) {
                            module.debug('Forcing partial selection to selected item', $selectedItem);
                            module.event.item.click.call($selectedItem, {}, true);
                            return;
                        }
                        else {
                            if (settings.allowAdditions) {
                                module.set.selected(module.get.query());
                                module.remove.searchTerm();
                            }
                            else {
                                module.remove.searchTerm();
                            }
                        }
                    },

                    change: {
                        values: function (values) {
                            if (!settings.allowAdditions) {
                                module.clear();
                            }
                            module.debug('Creating dropdown with specified values', values);
                            module.setup.menu({values: values});
                            $.each(values, function (index, item) {
                                if (item.selected == true) {
                                    module.debug('Setting initial selection to', item.value);
                                    module.set.selected(item.value);
                                    return true;
                                }
                            });
                        }
                    },

                    event: {
                        change: function () {
                            if (!internalChange) {
                                module.debug('Input changed, updating selection');
                                module.set.selected();
                            }
                        },
                        focus: function () {
                            if (settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
                                module.show();
                            }
                        },
                        blur: function (event) {
                            pageLostFocus = (document.activeElement === this);
                            if (!activated && !pageLostFocus) {
                                module.remove.activeLabel();
                                module.hide();
                            }
                        },
                        mousedown: function () {
                            if (module.is.searchSelection()) {
                                // prevent menu hiding on immediate re-focus
                                willRefocus = true;
                            }
                            else {
                                // prevents focus callback from occurring on mousedown
                                activated = true;
                            }
                        },
                        mouseup: function () {
                            if (module.is.searchSelection()) {
                                // prevent menu hiding on immediate re-focus
                                willRefocus = false;
                            }
                            else {
                                activated = false;
                            }
                        },
                        click: function (event) {
                            var
                                $target = $(event.target)
                            ;
                            // focus search
                            if ($target.is($module)) {
                                if (!module.is.focusedOnSearch()) {
                                    module.focusSearch();
                                }
                                else {
                                    module.show();
                                }
                            }
                        },
                        search: {
                            focus: function () {
                                activated = true;
                                if (module.is.multiple()) {
                                    module.remove.activeLabel();
                                }
                                if (settings.showOnFocus) {
                                    module.search();
                                }
                            },
                            blur: function (event) {
                                pageLostFocus = (document.activeElement === this);
                                if (module.is.searchSelection() && !willRefocus) {
                                    if (!itemActivated && !pageLostFocus) {
                                        if (settings.forceSelection) {
                                            module.forceSelection();
                                        }
                                        module.hide();
                                    }
                                }
                                willRefocus = false;
                            }
                        },
                        icon: {
                            click: function (event) {
                                if ($icon.hasClass(className.clear)) {
                                    module.clear();
                                }
                                else if (module.can.click()) {
                                    module.toggle();
                                }
                            }
                        },
                        text: {
                            focus: function (event) {
                                activated = true;
                                module.focusSearch();
                            }
                        },
                        input: function (event) {
                            if (module.is.multiple() || module.is.searchSelection()) {
                                module.set.filtered();
                            }
                            clearTimeout(module.timer);
                            module.timer = setTimeout(module.search, settings.delay.search);
                        },
                        label: {
                            click: function (event) {
                                var
                                    $label = $(this),
                                    $labels = $module.find(selector.label),
                                    $activeLabels = $labels.filter('.' + className.active),
                                    $nextActive = $label.nextAll('.' + className.active),
                                    $prevActive = $label.prevAll('.' + className.active),
                                    $range = ($nextActive.length > 0)
                                        ? $label.nextUntil($nextActive).add($activeLabels).add($label)
                                        : $label.prevUntil($prevActive).add($activeLabels).add($label)
                                ;
                                if (event.shiftKey) {
                                    $activeLabels.removeClass(className.active);
                                    $range.addClass(className.active);
                                }
                                else if (event.ctrlKey) {
                                    $label.toggleClass(className.active);
                                }
                                else {
                                    $activeLabels.removeClass(className.active);
                                    $label.addClass(className.active);
                                }
                                settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
                            }
                        },
                        remove: {
                            click: function () {
                                var
                                    $label = $(this).parent()
                                ;
                                if ($label.hasClass(className.active)) {
                                    // remove all selected labels
                                    module.remove.activeLabels();
                                }
                                else {
                                    // remove this label only
                                    module.remove.activeLabels($label);
                                }
                            }
                        },
                        test: {
                            toggle: function (event) {
                                var
                                    toggleBehavior = (module.is.multiple())
                                        ? module.show
                                        : module.toggle
                                ;
                                if (module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
                                    return;
                                }
                                if (module.determine.eventOnElement(event, toggleBehavior)) {
                                    event.preventDefault();
                                }
                            },
                            touch: function (event) {
                                module.determine.eventOnElement(event, function () {
                                    if (event.type == 'touchstart') {
                                        module.timer = setTimeout(function () {
                                            module.hide();
                                        }, settings.delay.touch);
                                    }
                                    else if (event.type == 'touchmove') {
                                        clearTimeout(module.timer);
                                    }
                                });
                                event.stopPropagation();
                            },
                            hide: function (event) {
                                module.determine.eventInModule(event, module.hide);
                            }
                        },
                        select: {
                            mutation: function (mutations) {
                                module.debug('<select> modified, recreating menu');
                                var
                                    isSelectMutation = false
                                ;
                                $.each(mutations, function (index, mutation) {
                                    if ($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) {
                                        isSelectMutation = true;
                                        return true;
                                    }
                                });
                                if (isSelectMutation) {
                                    module.disconnect.selectObserver();
                                    module.refresh();
                                    module.setup.select();
                                    module.set.selected();
                                    module.observe.select();
                                }
                            }
                        },
                        menu: {
                            mutation: function (mutations) {
                                var
                                    mutation = mutations[0],
                                    $addedNode = mutation.addedNodes
                                        ? $(mutation.addedNodes[0])
                                        : $(false),
                                    $removedNode = mutation.removedNodes
                                        ? $(mutation.removedNodes[0])
                                        : $(false),
                                    $changedNodes = $addedNode.add($removedNode),
                                    isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0,
                                    isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0
                                ;
                                if (isUserAddition || isMessage) {
                                    module.debug('Updating item selector cache');
                                    module.refreshItems();
                                }
                                else {
                                    module.debug('Menu modified, updating selector cache');
                                    module.refresh();
                                }
                            },
                            mousedown: function () {
                                itemActivated = true;
                            },
                            mouseup: function () {
                                itemActivated = false;
                            }
                        },
                        item: {
                            mouseenter: function (event) {
                                var
                                    $target = $(event.target),
                                    $item = $(this),
                                    $subMenu = $item.children(selector.menu),
                                    $otherMenus = $item.siblings(selector.item).children(selector.menu),
                                    hasSubMenu = ($subMenu.length > 0),
                                    isBubbledEvent = ($subMenu.find($target).length > 0)
                                ;
                                if (!isBubbledEvent && hasSubMenu) {
                                    clearTimeout(module.itemTimer);
                                    module.itemTimer = setTimeout(function () {
                                        module.verbose('Showing sub-menu', $subMenu);
                                        $.each($otherMenus, function () {
                                            module.animate.hide(false, $(this));
                                        });
                                        module.animate.show(false, $subMenu);
                                    }, settings.delay.show);
                                    event.preventDefault();
                                }
                            },
                            mouseleave: function (event) {
                                var
                                    $subMenu = $(this).children(selector.menu)
                                ;
                                if ($subMenu.length > 0) {
                                    clearTimeout(module.itemTimer);
                                    module.itemTimer = setTimeout(function () {
                                        module.verbose('Hiding sub-menu', $subMenu);
                                        module.animate.hide(false, $subMenu);
                                    }, settings.delay.hide);
                                }
                            },
                            click: function (event, skipRefocus) {
                                var
                                    $choice = $(this),
                                    $target = (event)
                                        ? $(event.target)
                                        : $(''),
                                    $subMenu = $choice.find(selector.menu),
                                    text = module.get.choiceText($choice),
                                    value = module.get.choiceValue($choice, text),
                                    hasSubMenu = ($subMenu.length > 0),
                                    isBubbledEvent = ($subMenu.find($target).length > 0)
                                ;
                                // prevents IE11 bug where menu receives focus even though `tabindex=-1`
                                if (module.has.menuSearch()) {
                                    $(document.activeElement).blur();
                                }
                                if (!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
                                    if (module.is.searchSelection()) {
                                        if (settings.allowAdditions) {
                                            module.remove.userAddition();
                                        }
                                        module.remove.searchTerm();
                                        if (!module.is.focusedOnSearch() && !(skipRefocus == true)) {
                                            module.focusSearch(true);
                                        }
                                    }
                                    if (!settings.useLabels) {
                                        module.remove.filteredItem();
                                        module.set.scrollPosition($choice);
                                    }
                                    module.determine.selectAction.call(this, text, value);
                                }
                            }
                        },

                        document: {
                            // label selection should occur even when element has no focus
                            keydown: function (event) {
                                var
                                    pressedKey = event.which,
                                    isShortcutKey = module.is.inObject(pressedKey, keys)
                                ;
                                if (isShortcutKey) {
                                    var
                                        $label = $module.find(selector.label),
                                        $activeLabel = $label.filter('.' + className.active),
                                        activeValue = $activeLabel.data(metadata.value),
                                        labelIndex = $label.index($activeLabel),
                                        labelCount = $label.length,
                                        hasActiveLabel = ($activeLabel.length > 0),
                                        hasMultipleActive = ($activeLabel.length > 1),
                                        isFirstLabel = (labelIndex === 0),
                                        isLastLabel = (labelIndex + 1 == labelCount),
                                        isSearch = module.is.searchSelection(),
                                        isFocusedOnSearch = module.is.focusedOnSearch(),
                                        isFocused = module.is.focused(),
                                        caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
                                        $nextLabel
                                    ;
                                    if (isSearch && !hasActiveLabel && !isFocusedOnSearch) {
                                        return;
                                    }

                                    if (pressedKey == keys.leftArrow) {
                                        // activate previous label
                                        if ((isFocused || caretAtStart) && !hasActiveLabel) {
                                            module.verbose('Selecting previous label');
                                            $label.last().addClass(className.active);
                                        }
                                        else if (hasActiveLabel) {
                                            if (!event.shiftKey) {
                                                module.verbose('Selecting previous label');
                                                $label.removeClass(className.active);
                                            }
                                            else {
                                                module.verbose('Adding previous label to selection');
                                            }
                                            if (isFirstLabel && !hasMultipleActive) {
                                                $activeLabel.addClass(className.active);
                                            }
                                            else {
                                                $activeLabel.prev(selector.siblingLabel)
                                                    .addClass(className.active)
                                                    .end()
                                                ;
                                            }
                                            event.preventDefault();
                                        }
                                    }
                                    else if (pressedKey == keys.rightArrow) {
                                        // activate first label
                                        if (isFocused && !hasActiveLabel) {
                                            $label.first().addClass(className.active);
                                        }
                                        // activate next label
                                        if (hasActiveLabel) {
                                            if (!event.shiftKey) {
                                                module.verbose('Selecting next label');
                                                $label.removeClass(className.active);
                                            }
                                            else {
                                                module.verbose('Adding next label to selection');
                                            }
                                            if (isLastLabel) {
                                                if (isSearch) {
                                                    if (!isFocusedOnSearch) {
                                                        module.focusSearch();
                                                    }
                                                    else {
                                                        $label.removeClass(className.active);
                                                    }
                                                }
                                                else if (hasMultipleActive) {
                                                    $activeLabel.next(selector.siblingLabel).addClass(className.active);
                                                }
                                                else {
                                                    $activeLabel.addClass(className.active);
                                                }
                                            }
                                            else {
                                                $activeLabel.next(selector.siblingLabel).addClass(className.active);
                                            }
                                            event.preventDefault();
                                        }
                                    }
                                    else if (pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
                                        if (hasActiveLabel) {
                                            module.verbose('Removing active labels');
                                            if (isLastLabel) {
                                                if (isSearch && !isFocusedOnSearch) {
                                                    module.focusSearch();
                                                }
                                            }
                                            $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
                                            module.remove.activeLabels($activeLabel);
                                            event.preventDefault();
                                        }
                                        else if (caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
                                            module.verbose('Removing last label on input backspace');
                                            $activeLabel = $label.last().addClass(className.active);
                                            module.remove.activeLabels($activeLabel);
                                        }
                                    }
                                    else {
                                        $activeLabel.removeClass(className.active);
                                    }
                                }
                            }
                        },

                        keydown: function (event) {
                            var
                                pressedKey = event.which,
                                isShortcutKey = module.is.inObject(pressedKey, keys)
                            ;
                            if (isShortcutKey) {
                                var
                                    $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
                                    $activeItem = $menu.children('.' + className.active).eq(0),
                                    $selectedItem = ($currentlySelected.length > 0)
                                        ? $currentlySelected
                                        : $activeItem,
                                    $visibleItems = ($selectedItem.length > 0)
                                        ? $selectedItem.siblings(':not(.' + className.filtered + ')').addBack()
                                        : $menu.children(':not(.' + className.filtered + ')'),
                                    $subMenu = $selectedItem.children(selector.menu),
                                    $parentMenu = $selectedItem.closest(selector.menu),
                                    inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
                                    hasSubMenu = ($subMenu.length > 0),
                                    hasSelectedItem = ($selectedItem.length > 0),
                                    selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0),
                                    delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()),
                                    isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable),
                                    $nextItem,
                                    isSubMenuItem,
                                    newIndex
                                ;
                                // allow selection with menu closed
                                if (isAdditionWithoutMenu) {
                                    module.verbose('Selecting item from keyboard shortcut', $selectedItem);
                                    module.event.item.click.call($selectedItem, event);
                                    if (module.is.searchSelection()) {
                                        module.remove.searchTerm();
                                    }
                                }

                                // visible menu keyboard shortcuts
                                if (module.is.visible()) {

                                    // enter (select or open sub-menu)
                                    if (pressedKey == keys.enter || delimiterPressed) {
                                        if (pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
                                            module.verbose('Pressed enter on unselectable category, opening sub menu');
                                            pressedKey = keys.rightArrow;
                                        }
                                        else if (selectedIsSelectable) {
                                            module.verbose('Selecting item from keyboard shortcut', $selectedItem);
                                            module.event.item.click.call($selectedItem, event);
                                            if (module.is.searchSelection()) {
                                                module.remove.searchTerm();
                                            }
                                        }
                                        event.preventDefault();
                                    }

                                    // sub-menu actions
                                    if (hasSelectedItem) {

                                        if (pressedKey == keys.leftArrow) {

                                            isSubMenuItem = ($parentMenu[0] !== $menu[0]);

                                            if (isSubMenuItem) {
                                                module.verbose('Left key pressed, closing sub-menu');
                                                module.animate.hide(false, $parentMenu);
                                                $selectedItem
                                                    .removeClass(className.selected)
                                                ;
                                                $parentMenu
                                                    .closest(selector.item)
                                                    .addClass(className.selected)
                                                ;
                                                event.preventDefault();
                                            }
                                        }

                                        // right arrow (show sub-menu)
                                        if (pressedKey == keys.rightArrow) {
                                            if (hasSubMenu) {
                                                module.verbose('Right key pressed, opening sub-menu');
                                                module.animate.show(false, $subMenu);
                                                $selectedItem
                                                    .removeClass(className.selected)
                                                ;
                                                $subMenu
                                                    .find(selector.item).eq(0)
                                                    .addClass(className.selected)
                                                ;
                                                event.preventDefault();
                                            }
                                        }
                                    }

                                    // up arrow (traverse menu up)
                                    if (pressedKey == keys.upArrow) {
                                        $nextItem = (hasSelectedItem && inVisibleMenu)
                                            ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
                                            : $item.eq(0)
                                        ;
                                        if ($visibleItems.index($nextItem) < 0) {
                                            module.verbose('Up key pressed but reached top of current menu');
                                            event.preventDefault();
                                            return;
                                        }
                                        else {
                                            module.verbose('Up key pressed, changing active item');
                                            $selectedItem
                                                .removeClass(className.selected)
                                            ;
                                            $nextItem
                                                .addClass(className.selected)
                                            ;
                                            module.set.scrollPosition($nextItem);
                                            if (settings.selectOnKeydown && module.is.single()) {
                                                module.set.selectedItem($nextItem);
                                            }
                                        }
                                        event.preventDefault();
                                    }

                                    // down arrow (traverse menu down)
                                    if (pressedKey == keys.downArrow) {
                                        $nextItem = (hasSelectedItem && inVisibleMenu)
                                            ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
                                            : $item.eq(0)
                                        ;
                                        if ($nextItem.length === 0) {
                                            module.verbose('Down key pressed but reached bottom of current menu');
                                            event.preventDefault();
                                            return;
                                        }
                                        else {
                                            module.verbose('Down key pressed, changing active item');
                                            $item
                                                .removeClass(className.selected)
                                            ;
                                            $nextItem
                                                .addClass(className.selected)
                                            ;
                                            module.set.scrollPosition($nextItem);
                                            if (settings.selectOnKeydown && module.is.single()) {
                                                module.set.selectedItem($nextItem);
                                            }
                                        }
                                        event.preventDefault();
                                    }

                                    // page down (show next page)
                                    if (pressedKey == keys.pageUp) {
                                        module.scrollPage('up');
                                        event.preventDefault();
                                    }
                                    if (pressedKey == keys.pageDown) {
                                        module.scrollPage('down');
                                        event.preventDefault();
                                    }

                                    // escape (close menu)
                                    if (pressedKey == keys.escape) {
                                        module.verbose('Escape key pressed, closing dropdown');
                                        module.hide();
                                    }

                                }
                                else {
                                    // delimiter key
                                    if (delimiterPressed) {
                                        event.preventDefault();
                                    }
                                    // down arrow (open menu)
                                    if (pressedKey == keys.downArrow && !module.is.visible()) {
                                        module.verbose('Down key pressed, showing dropdown');
                                        module.show();
                                        event.preventDefault();
                                    }
                                }
                            }
                            else {
                                if (!module.has.search()) {
                                    module.set.selectedLetter(String.fromCharCode(pressedKey));
                                }
                            }
                        }
                    },

                    trigger: {
                        change: function () {
                            var
                                events = document.createEvent('HTMLEvents'),
                                inputElement = $input[0]
                            ;
                            if (inputElement) {
                                module.verbose('Triggering native change event');
                                events.initEvent('change', true, false);
                                inputElement.dispatchEvent(events);
                            }
                        }
                    },

                    determine: {
                        selectAction: function (text, value) {
                            module.verbose('Determining action', settings.action);
                            if ($.isFunction(module.action[settings.action])) {
                                module.verbose('Triggering preset action', settings.action, text, value);
                                module.action[settings.action].call(element, text, value, this);
                            }
                            else if ($.isFunction(settings.action)) {
                                module.verbose('Triggering user action', settings.action, text, value);
                                settings.action.call(element, text, value, this);
                            }
                            else {
                                module.error(error.action, settings.action);
                            }
                        },
                        eventInModule: function (event, callback) {
                            var
                                $target = $(event.target),
                                inDocument = ($target.closest(document.documentElement).length > 0),
                                inModule = ($target.closest($module).length > 0)
                            ;
                            callback = $.isFunction(callback)
                                ? callback
                                : function () {
                                }
                            ;
                            if (inDocument && !inModule) {
                                module.verbose('Triggering event', callback);
                                callback();
                                return true;
                            }
                            else {
                                module.verbose('Event occurred in dropdown, canceling callback');
                                return false;
                            }
                        },
                        eventOnElement: function (event, callback) {
                            var
                                $target = $(event.target),
                                $label = $target.closest(selector.siblingLabel),
                                inVisibleDOM = document.body.contains(event.target),
                                notOnLabel = ($module.find($label).length === 0),
                                notInMenu = ($target.closest($menu).length === 0)
                            ;
                            callback = $.isFunction(callback)
                                ? callback
                                : function () {
                                }
                            ;
                            if (inVisibleDOM && notOnLabel && notInMenu) {
                                module.verbose('Triggering event', callback);
                                callback();
                                return true;
                            }
                            else {
                                module.verbose('Event occurred in dropdown menu, canceling callback');
                                return false;
                            }
                        }
                    },

                    action: {

                        nothing: function () {
                        },

                        activate: function (text, value, element) {
                            value = (value !== undefined)
                                ? value
                                : text
                            ;
                            if (module.can.activate($(element))) {
                                module.set.selected(value, $(element));
                                if (module.is.multiple() && !module.is.allFiltered()) {
                                    return;
                                }
                                else {
                                    module.hideAndClear();
                                }
                            }
                        },

                        select: function (text, value, element) {
                            value = (value !== undefined)
                                ? value
                                : text
                            ;
                            if (module.can.activate($(element))) {
                                module.set.value(value, text, $(element));
                                if (module.is.multiple() && !module.is.allFiltered()) {
                                    return;
                                }
                                else {
                                    module.hideAndClear();
                                }
                            }
                        },

                        combo: function (text, value, element) {
                            value = (value !== undefined)
                                ? value
                                : text
                            ;
                            module.set.selected(value, $(element));
                            module.hideAndClear();
                        },

                        hide: function (text, value, element) {
                            module.set.value(value, text, $(element));
                            module.hideAndClear();
                        }

                    },

                    get: {
                        id: function () {
                            return id;
                        },
                        defaultText: function () {
                            return $module.data(metadata.defaultText);
                        },
                        defaultValue: function () {
                            return $module.data(metadata.defaultValue);
                        },
                        placeholderText: function () {
                            if (settings.placeholder != 'auto' && typeof settings.placeholder == 'string') {
                                return settings.placeholder;
                            }
                            return $module.data(metadata.placeholderText) || '';
                        },
                        text: function () {
                            return $text.text();
                        },
                        query: function () {
                            return $.trim($search.val());
                        },
                        searchWidth: function (value) {
                            value = (value !== undefined)
                                ? value
                                : $search.val()
                            ;
                            $sizer.text(value);
                            // prevent rounding issues
                            return Math.ceil($sizer.width() + 1);
                        },
                        selectionCount: function () {
                            var
                                values = module.get.values(),
                                count
                            ;
                            count = (module.is.multiple())
                                ? $.isArray(values)
                                    ? values.length
                                    : 0
                                : (module.get.value() !== '')
                                    ? 1
                                    : 0
                            ;
                            return count;
                        },
                        transition: function ($subMenu) {
                            return (settings.transition == 'auto')
                                ? module.is.upward($subMenu)
                                    ? 'slide up'
                                    : 'slide down'
                                : settings.transition
                                ;
                        },
                        userValues: function () {
                            var
                                values = module.get.values()
                            ;
                            if (!values) {
                                return false;
                            }
                            values = $.isArray(values)
                                ? values
                                : [values]
                            ;
                            return $.grep(values, function (value) {
                                return (module.get.item(value) === false);
                            });
                        },
                        uniqueArray: function (array) {
                            return $.grep(array, function (value, index) {
                                return $.inArray(value, array) === index;
                            });
                        },
                        caretPosition: function () {
                            var
                                input = $search.get(0),
                                range,
                                rangeLength
                            ;
                            if ('selectionStart' in input) {
                                return input.selectionStart;
                            }
                            else if (document.selection) {
                                input.focus();
                                range = document.selection.createRange();
                                rangeLength = range.text.length;
                                range.moveStart('character', -input.value.length);
                                return range.text.length - rangeLength;
                            }
                        },
                        value: function () {
                            var
                                value = ($input.length > 0)
                                    ? $input.val()
                                    : $module.data(metadata.value),
                                isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
                            ;
                            // prevents placeholder element from being selected when multiple
                            return (value === undefined || isEmptyMultiselect)
                                ? ''
                                : value
                                ;
                        },
                        values: function () {
                            var
                                value = module.get.value()
                            ;
                            if (value === '') {
                                return '';
                            }
                            return (!module.has.selectInput() && module.is.multiple())
                                ? (typeof value == 'string') // delimited string
                                    ? value.split(settings.delimiter)
                                    : ''
                                : value
                                ;
                        },
                        remoteValues: function () {
                            var
                                values = module.get.values(),
                                remoteValues = false
                            ;
                            if (values) {
                                if (typeof values == 'string') {
                                    values = [values];
                                }
                                $.each(values, function (index, value) {
                                    var
                                        name = module.read.remoteData(value)
                                    ;
                                    module.verbose('Restoring value from session data', name, value);
                                    if (name) {
                                        if (!remoteValues) {
                                            remoteValues = {};
                                        }
                                        remoteValues[value] = name;
                                    }
                                });
                            }
                            return remoteValues;
                        },
                        choiceText: function ($choice, preserveHTML) {
                            preserveHTML = (preserveHTML !== undefined)
                                ? preserveHTML
                                : settings.preserveHTML
                            ;
                            if ($choice) {
                                if ($choice.find(selector.menu).length > 0) {
                                    module.verbose('Retrieving text of element with sub-menu');
                                    $choice = $choice.clone();
                                    $choice.find(selector.menu).remove();
                                    $choice.find(selector.menuIcon).remove();
                                }
                                return ($choice.data(metadata.text) !== undefined)
                                    ? $choice.data(metadata.text)
                                    : (preserveHTML)
                                        ? $.trim($choice.html())
                                        : $.trim($choice.text())
                                    ;
                            }
                        },
                        choiceValue: function ($choice, choiceText) {
                            choiceText = choiceText || module.get.choiceText($choice);
                            if (!$choice) {
                                return false;
                            }
                            return ($choice.data(metadata.value) !== undefined)
                                ? String($choice.data(metadata.value))
                                : (typeof choiceText === 'string')
                                    ? $.trim(choiceText.toLowerCase())
                                    : String(choiceText)
                                ;
                        },
                        inputEvent: function () {
                            var
                                input = $search[0]
                            ;
                            if (input) {
                                return (input.oninput !== undefined)
                                    ? 'input'
                                    : (input.onpropertychange !== undefined)
                                        ? 'propertychange'
                                        : 'keyup'
                                    ;
                            }
                            return false;
                        },
                        selectValues: function () {
                            var
                                select = {}
                            ;
                            select.values = [];
                            $module
                                .find('option')
                                .each(function () {
                                    var
                                        $option = $(this),
                                        name = $option.html(),
                                        disabled = $option.attr('disabled'),
                                        value = ($option.attr('value') !== undefined)
                                            ? $option.attr('value')
                                            : name
                                    ;
                                    if (settings.placeholder === 'auto' && value === '') {
                                        select.placeholder = name;
                                    }
                                    else {
                                        select.values.push({
                                            name: name,
                                            value: value,
                                            disabled: disabled
                                        });
                                    }
                                })
                            ;
                            if (settings.placeholder && settings.placeholder !== 'auto') {
                                module.debug('Setting placeholder value to', settings.placeholder);
                                select.placeholder = settings.placeholder;
                            }
                            if (settings.sortSelect) {
                                select.values.sort(function (a, b) {
                                    return (a.name > b.name)
                                        ? 1
                                        : -1
                                        ;
                                });
                                module.debug('Retrieved and sorted values from select', select);
                            }
                            else {
                                module.debug('Retrieved values from select', select);
                            }
                            return select;
                        },
                        activeItem: function () {
                            return $item.filter('.' + className.active);
                        },
                        selectedItem: function () {
                            var
                                $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
                            ;
                            return ($selectedItem.length > 0)
                                ? $selectedItem
                                : $item.eq(0)
                                ;
                        },
                        itemWithAdditions: function (value) {
                            var
                                $items = module.get.item(value),
                                $userItems = module.create.userChoice(value),
                                hasUserItems = ($userItems && $userItems.length > 0)
                            ;
                            if (hasUserItems) {
                                $items = ($items.length > 0)
                                    ? $items.add($userItems)
                                    : $userItems
                                ;
                            }
                            return $items;
                        },
                        item: function (value, strict) {
                            var
                                $selectedItem = false,
                                shouldSearch,
                                isMultiple
                            ;
                            value = (value !== undefined)
                                ? value
                                : (module.get.values() !== undefined)
                                    ? module.get.values()
                                    : module.get.text()
                            ;
                            shouldSearch = (isMultiple)
                                ? (value.length > 0)
                                : (value !== undefined && value !== null)
                            ;
                            isMultiple = (module.is.multiple() && $.isArray(value));
                            strict = (value === '' || value === 0)
                                ? true
                                : strict || false
                            ;
                            if (shouldSearch) {
                                $item
                                    .each(function () {
                                        var
                                            $choice = $(this),
                                            optionText = module.get.choiceText($choice),
                                            optionValue = module.get.choiceValue($choice, optionText)
                                        ;
                                        // safe early exit
                                        if (optionValue === null || optionValue === undefined) {
                                            return;
                                        }
                                        if (isMultiple) {
                                            if ($.inArray(String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) {
                                                $selectedItem = ($selectedItem)
                                                    ? $selectedItem.add($choice)
                                                    : $choice
                                                ;
                                            }
                                        }
                                        else if (strict) {
                                            module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
                                            if (optionValue === value || optionText === value) {
                                                $selectedItem = $choice;
                                                return true;
                                            }
                                        }
                                        else {
                                            if (String(optionValue) == String(value) || optionText == value) {
                                                module.verbose('Found select item by value', optionValue, value);
                                                $selectedItem = $choice;
                                                return true;
                                            }
                                        }
                                    })
                                ;
                            }
                            return $selectedItem;
                        }
                    },

                    check: {
                        maxSelections: function (selectionCount) {
                            if (settings.maxSelections) {
                                selectionCount = (selectionCount !== undefined)
                                    ? selectionCount
                                    : module.get.selectionCount()
                                ;
                                if (selectionCount >= settings.maxSelections) {
                                    module.debug('Maximum selection count reached');
                                    if (settings.useLabels) {
                                        $item.addClass(className.filtered);
                                        module.add.message(message.maxSelections);
                                    }
                                    return true;
                                }
                                else {
                                    module.verbose('No longer at maximum selection count');
                                    module.remove.message();
                                    module.remove.filteredItem();
                                    if (module.is.searchSelection()) {
                                        module.filterItems();
                                    }
                                    return false;
                                }
                            }
                            return true;
                        }
                    },

                    restore: {
                        defaults: function () {
                            module.clear();
                            module.restore.defaultText();
                            module.restore.defaultValue();
                        },
                        defaultText: function () {
                            var
                                defaultText = module.get.defaultText(),
                                placeholderText = module.get.placeholderText
                            ;
                            if (defaultText === placeholderText) {
                                module.debug('Restoring default placeholder text', defaultText);
                                module.set.placeholderText(defaultText);
                            }
                            else {
                                module.debug('Restoring default text', defaultText);
                                module.set.text(defaultText);
                            }
                        },
                        placeholderText: function () {
                            module.set.placeholderText();
                        },
                        defaultValue: function () {
                            var
                                defaultValue = module.get.defaultValue()
                            ;
                            if (defaultValue !== undefined) {
                                module.debug('Restoring default value', defaultValue);
                                if (defaultValue !== '') {
                                    module.set.value(defaultValue);
                                    module.set.selected();
                                }
                                else {
                                    module.remove.activeItem();
                                    module.remove.selectedItem();
                                }
                            }
                        },
                        labels: function () {
                            if (settings.allowAdditions) {
                                if (!settings.useLabels) {
                                    module.error(error.labels);
                                    settings.useLabels = true;
                                }
                                module.debug('Restoring selected values');
                                module.create.userLabels();
                            }
                            module.check.maxSelections();
                        },
                        selected: function () {
                            module.restore.values();
                            if (module.is.multiple()) {
                                module.debug('Restoring previously selected values and labels');
                                module.restore.labels();
                            }
                            else {
                                module.debug('Restoring previously selected values');
                            }
                        },
                        values: function () {
                            // prevents callbacks from occurring on initial load
                            module.set.initialLoad();
                            if (settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
                                module.restore.remoteValues();
                            }
                            else {
                                module.set.selected();
                            }
                            module.remove.initialLoad();
                        },
                        remoteValues: function () {
                            var
                                values = module.get.remoteValues()
                            ;
                            module.debug('Recreating selected from session data', values);
                            if (values) {
                                if (module.is.single()) {
                                    $.each(values, function (value, name) {
                                        module.set.text(name);
                                    });
                                }
                                else {
                                    $.each(values, function (value, name) {
                                        module.add.label(value, name);
                                    });
                                }
                            }
                        }
                    },

                    read: {
                        remoteData: function (value) {
                            var
                                name
                            ;
                            if (window.Storage === undefined) {
                                module.error(error.noStorage);
                                return;
                            }
                            name = sessionStorage.getItem(value);
                            return (name !== undefined)
                                ? name
                                : false
                                ;
                        }
                    },

                    save: {
                        defaults: function () {
                            module.save.defaultText();
                            module.save.placeholderText();
                            module.save.defaultValue();
                        },
                        defaultValue: function () {
                            var
                                value = module.get.value()
                            ;
                            module.verbose('Saving default value as', value);
                            $module.data(metadata.defaultValue, value);
                        },
                        defaultText: function () {
                            var
                                text = module.get.text()
                            ;
                            module.verbose('Saving default text as', text);
                            $module.data(metadata.defaultText, text);
                        },
                        placeholderText: function () {
                            var
                                text
                            ;
                            if (settings.placeholder !== false && $text.hasClass(className.placeholder)) {
                                text = module.get.text();
                                module.verbose('Saving placeholder text as', text);
                                $module.data(metadata.placeholderText, text);
                            }
                        },
                        remoteData: function (name, value) {
                            if (window.Storage === undefined) {
                                module.error(error.noStorage);
                                return;
                            }
                            module.verbose('Saving remote data to session storage', value, name);
                            sessionStorage.setItem(value, name);
                        }
                    },

                    clear: function () {
                        if (module.is.multiple() && settings.useLabels) {
                            module.remove.labels();
                        }
                        else {
                            module.remove.activeItem();
                            module.remove.selectedItem();
                        }
                        module.set.placeholderText();
                        module.clearValue();
                    },

                    clearValue: function () {
                        module.set.value('');
                    },

                    scrollPage: function (direction, $selectedItem) {
                        var
                            $currentItem = $selectedItem || module.get.selectedItem(),
                            $menu = $currentItem.closest(selector.menu),
                            menuHeight = $menu.outerHeight(),
                            currentScroll = $menu.scrollTop(),
                            itemHeight = $item.eq(0).outerHeight(),
                            itemsPerPage = Math.floor(menuHeight / itemHeight),
                            maxScroll = $menu.prop('scrollHeight'),
                            newScroll = (direction == 'up')
                                ? currentScroll - (itemHeight * itemsPerPage)
                                : currentScroll + (itemHeight * itemsPerPage),
                            $selectableItem = $item.not(selector.unselectable),
                            isWithinRange,
                            $nextSelectedItem,
                            elementIndex
                        ;
                        elementIndex = (direction == 'up')
                            ? $selectableItem.index($currentItem) - itemsPerPage
                            : $selectableItem.index($currentItem) + itemsPerPage
                        ;
                        isWithinRange = (direction == 'up')
                            ? (elementIndex >= 0)
                            : (elementIndex < $selectableItem.length)
                        ;
                        $nextSelectedItem = (isWithinRange)
                            ? $selectableItem.eq(elementIndex)
                            : (direction == 'up')
                                ? $selectableItem.first()
                                : $selectableItem.last()
                        ;
                        if ($nextSelectedItem.length > 0) {
                            module.debug('Scrolling page', direction, $nextSelectedItem);
                            $currentItem
                                .removeClass(className.selected)
                            ;
                            $nextSelectedItem
                                .addClass(className.selected)
                            ;
                            if (settings.selectOnKeydown && module.is.single()) {
                                module.set.selectedItem($nextSelectedItem);
                            }
                            $menu
                                .scrollTop(newScroll)
                            ;
                        }
                    },

                    set: {
                        filtered: function () {
                            var
                                isMultiple = module.is.multiple(),
                                isSearch = module.is.searchSelection(),
                                isSearchMultiple = (isMultiple && isSearch),
                                searchValue = (isSearch)
                                    ? module.get.query()
                                    : '',
                                hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
                                searchWidth = module.get.searchWidth(),
                                valueIsSet = searchValue !== ''
                            ;
                            if (isMultiple && hasSearchValue) {
                                module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
                                $search.css('width', searchWidth);
                            }
                            if (hasSearchValue || (isSearchMultiple && valueIsSet)) {
                                module.verbose('Hiding placeholder text');
                                $text.addClass(className.filtered);
                            }
                            else if (!isMultiple || (isSearchMultiple && !valueIsSet)) {
                                module.verbose('Showing placeholder text');
                                $text.removeClass(className.filtered);
                            }
                        },
                        empty: function () {
                            $module.addClass(className.empty);
                        },
                        loading: function () {
                            $module.addClass(className.loading);
                        },
                        placeholderText: function (text) {
                            text = text || module.get.placeholderText();
                            module.debug('Setting placeholder text', text);
                            module.set.text(text);
                            $text.addClass(className.placeholder);
                        },
                        tabbable: function () {
                            if (module.is.searchSelection()) {
                                module.debug('Added tabindex to searchable dropdown');
                                $search
                                    .val('')
                                    .attr('tabindex', 0)
                                ;
                                $menu
                                    .attr('tabindex', -1)
                                ;
                            }
                            else {
                                module.debug('Added tabindex to dropdown');
                                if ($module.attr('tabindex') === undefined) {
                                    $module
                                        .attr('tabindex', 0)
                                    ;
                                    $menu
                                        .attr('tabindex', -1)
                                    ;
                                }
                            }
                        },
                        initialLoad: function () {
                            module.verbose('Setting initial load');
                            initialLoad = true;
                        },
                        activeItem: function ($item) {
                            if (settings.allowAdditions && $item.filter(selector.addition).length > 0) {
                                $item.addClass(className.filtered);
                            }
                            else {
                                $item.addClass(className.active);
                            }
                        },
                        partialSearch: function (text) {
                            var
                                length = module.get.query().length
                            ;
                            $search.val(text.substr(0, length));
                        },
                        scrollPosition: function ($item, forceScroll) {
                            var
                                edgeTolerance = 5,
                                $menu,
                                hasActive,
                                offset,
                                itemHeight,
                                itemOffset,
                                menuOffset,
                                menuScroll,
                                menuHeight,
                                abovePage,
                                belowPage
                            ;

                            $item = $item || module.get.selectedItem();
                            $menu = $item.closest(selector.menu);
                            hasActive = ($item && $item.length > 0);
                            forceScroll = (forceScroll !== undefined)
                                ? forceScroll
                                : false
                            ;
                            if ($item && $menu.length > 0 && hasActive) {
                                itemOffset = $item.position().top;

                                $menu.addClass(className.loading);
                                menuScroll = $menu.scrollTop();
                                menuOffset = $menu.offset().top;
                                itemOffset = $item.offset().top;
                                offset = menuScroll - menuOffset + itemOffset;
                                if (!forceScroll) {
                                    menuHeight = $menu.height();
                                    belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
                                    abovePage = ((offset - edgeTolerance) < menuScroll);
                                }
                                module.debug('Scrolling to active item', offset);
                                if (forceScroll || abovePage || belowPage) {
                                    $menu.scrollTop(offset);
                                }
                                $menu.removeClass(className.loading);
                            }
                        },
                        text: function (text) {
                            if (settings.action !== 'select') {
                                if (settings.action == 'combo') {
                                    module.debug('Changing combo button text', text, $combo);
                                    if (settings.preserveHTML) {
                                        $combo.html(text);
                                    }
                                    else {
                                        $combo.text(text);
                                    }
                                }
                                else {
                                    if (text !== module.get.placeholderText()) {
                                        $text.removeClass(className.placeholder);
                                    }
                                    module.debug('Changing text', text, $text);
                                    $text
                                        .removeClass(className.filtered)
                                    ;
                                    if (settings.preserveHTML) {
                                        $text.html(text);
                                    }
                                    else {
                                        $text.text(text);
                                    }
                                }
                            }
                        },
                        selectedItem: function ($item) {
                            var
                                value = module.get.choiceValue($item),
                                searchText = module.get.choiceText($item, false),
                                text = module.get.choiceText($item, true)
                            ;
                            module.debug('Setting user selection to item', $item);
                            module.remove.activeItem();
                            module.set.partialSearch(searchText);
                            module.set.activeItem($item);
                            module.set.selected(value, $item);
                            module.set.text(text);
                        },
                        selectedLetter: function (letter) {
                            var
                                $selectedItem = $item.filter('.' + className.selected),
                                alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
                                $nextValue = false,
                                $nextItem
                            ;
                            // check next of same letter
                            if (alreadySelectedLetter) {
                                $nextItem = $selectedItem.nextAll($item).eq(0);
                                if (module.has.firstLetter($nextItem, letter)) {
                                    $nextValue = $nextItem;
                                }
                            }
                            // check all values
                            if (!$nextValue) {
                                $item
                                    .each(function () {
                                        if (module.has.firstLetter($(this), letter)) {
                                            $nextValue = $(this);
                                            return false;
                                        }
                                    })
                                ;
                            }
                            // set next value
                            if ($nextValue) {
                                module.verbose('Scrolling to next value with letter', letter);
                                module.set.scrollPosition($nextValue);
                                $selectedItem.removeClass(className.selected);
                                $nextValue.addClass(className.selected);
                                if (settings.selectOnKeydown && module.is.single()) {
                                    module.set.selectedItem($nextValue);
                                }
                            }
                        },
                        direction: function ($menu) {
                            if (settings.direction == 'auto') {
                                // reset position
                                module.remove.upward();

                                if (module.can.openDownward($menu)) {
                                    module.remove.upward($menu);
                                }
                                else {
                                    module.set.upward($menu);
                                }
                                if (!module.is.leftward($menu) && !module.can.openRightward($menu)) {
                                    module.set.leftward($menu);
                                }
                            }
                            else if (settings.direction == 'upward') {
                                module.set.upward($menu);
                            }
                        },
                        upward: function ($currentMenu) {
                            var $element = $currentMenu || $module;
                            $element.addClass(className.upward);
                        },
                        leftward: function ($currentMenu) {
                            var $element = $currentMenu || $menu;
                            $element.addClass(className.leftward);
                        },
                        value: function (value, text, $selected) {
                            var
                                escapedValue = module.escape.value(value),
                                hasInput = ($input.length > 0),
                                currentValue = module.get.values(),
                                stringValue = (value !== undefined)
                                    ? String(value)
                                    : value,
                                newValue
                            ;
                            if (hasInput) {
                                if (!settings.allowReselection && stringValue == currentValue) {
                                    module.verbose('Skipping value update already same value', value, currentValue);
                                    if (!module.is.initialLoad()) {
                                        return;
                                    }
                                }

                                if (module.is.single() && module.has.selectInput() && module.can.extendSelect()) {
                                    module.debug('Adding user option', value);
                                    module.add.optionValue(value);
                                }
                                module.debug('Updating input value', escapedValue, currentValue);
                                internalChange = true;
                                $input
                                    .val(escapedValue)
                                ;
                                if (settings.fireOnInit === false && module.is.initialLoad()) {
                                    module.debug('Input native change event ignored on initial load');
                                }
                                else {
                                    module.trigger.change();
                                }
                                internalChange = false;
                            }
                            else {
                                module.verbose('Storing value in metadata', escapedValue, $input);
                                if (escapedValue !== currentValue) {
                                    $module.data(metadata.value, stringValue);
                                }
                            }
                            if (module.is.single() && settings.clearable) {
                                // treat undefined or '' as empty
                                if (!escapedValue) {
                                    module.remove.clearable();
                                }
                                else {
                                    module.set.clearable();
                                }
                            }
                            if (settings.fireOnInit === false && module.is.initialLoad()) {
                                module.verbose('No callback on initial load', settings.onChange);
                            }
                            else {
                                settings.onChange.call(element, value, text, $selected);
                            }
                        },
                        active: function () {
                            $module
                                .addClass(className.active)
                            ;
                        },
                        multiple: function () {
                            $module.addClass(className.multiple);
                        },
                        visible: function () {
                            $module.addClass(className.visible);
                        },
                        exactly: function (value, $selectedItem) {
                            module.debug('Setting selected to exact values');
                            module.clear();
                            module.set.selected(value, $selectedItem);
                        },
                        selected: function (value, $selectedItem) {
                            var
                                isMultiple = module.is.multiple(),
                                $userSelectedItem
                            ;
                            $selectedItem = (settings.allowAdditions)
                                ? $selectedItem || module.get.itemWithAdditions(value)
                                : $selectedItem || module.get.item(value)
                            ;
                            if (!$selectedItem) {
                                return;
                            }
                            module.debug('Setting selected menu item to', $selectedItem);
                            if (module.is.multiple()) {
                                module.remove.searchWidth();
                            }
                            if (module.is.single()) {
                                module.remove.activeItem();
                                module.remove.selectedItem();
                            }
                            else if (settings.useLabels) {
                                module.remove.selectedItem();
                            }
                            // select each item
                            $selectedItem
                                .each(function () {
                                    var
                                        $selected = $(this),
                                        selectedText = module.get.choiceText($selected),
                                        selectedValue = module.get.choiceValue($selected, selectedText),

                                        isFiltered = $selected.hasClass(className.filtered),
                                        isActive = $selected.hasClass(className.active),
                                        isUserValue = $selected.hasClass(className.addition),
                                        shouldAnimate = (isMultiple && $selectedItem.length == 1)
                                    ;
                                    if (isMultiple) {
                                        if (!isActive || isUserValue) {
                                            if (settings.apiSettings && settings.saveRemoteData) {
                                                module.save.remoteData(selectedText, selectedValue);
                                            }
                                            if (settings.useLabels) {
                                                module.add.label(selectedValue, selectedText, shouldAnimate);
                                                module.add.value(selectedValue, selectedText, $selected);
                                                module.set.activeItem($selected);
                                                module.filterActive();
                                                module.select.nextAvailable($selectedItem);
                                            }
                                            else {
                                                module.add.value(selectedValue, selectedText, $selected);
                                                module.set.text(module.add.variables(message.count));
                                                module.set.activeItem($selected);
                                            }
                                        }
                                        else if (!isFiltered) {
                                            module.debug('Selected active value, removing label');
                                            module.remove.selected(selectedValue);
                                        }
                                    }
                                    else {
                                        if (settings.apiSettings && settings.saveRemoteData) {
                                            module.save.remoteData(selectedText, selectedValue);
                                        }
                                        module.set.text(selectedText);
                                        module.set.value(selectedValue, selectedText, $selected);
                                        $selected
                                            .addClass(className.active)
                                            .addClass(className.selected)
                                        ;
                                    }
                                })
                            ;
                        },
                        clearable: function () {
                            $icon.addClass(className.clear);
                        },
                    },

                    add: {
                        label: function (value, text, shouldAnimate) {
                            var
                                $next = module.is.searchSelection()
                                    ? $search
                                    : $text,
                                escapedValue = module.escape.value(value),
                                $label
                            ;
                            if (settings.ignoreCase) {
                                escapedValue = escapedValue.toLowerCase();
                            }
                            $label = $('<a />')
                                .addClass(className.label)
                                .attr('data-' + metadata.value, escapedValue)
                                .html(templates.label(escapedValue, text))
                            ;
                            $label = settings.onLabelCreate.call($label, escapedValue, text);

                            if (module.has.label(value)) {
                                module.debug('User selection already exists, skipping', escapedValue);
                                return;
                            }
                            if (settings.label.variation) {
                                $label.addClass(settings.label.variation);
                            }
                            if (shouldAnimate === true) {
                                module.debug('Animating in label', $label);
                                $label
                                    .addClass(className.hidden)
                                    .insertBefore($next)
                                    .transition(settings.label.transition, settings.label.duration)
                                ;
                            }
                            else {
                                module.debug('Adding selection label', $label);
                                $label
                                    .insertBefore($next)
                                ;
                            }
                        },
                        message: function (message) {
                            var
                                $message = $menu.children(selector.message),
                                html = settings.templates.message(module.add.variables(message))
                            ;
                            if ($message.length > 0) {
                                $message
                                    .html(html)
                                ;
                            }
                            else {
                                $message = $('<div/>')
                                    .html(html)
                                    .addClass(className.message)
                                    .appendTo($menu)
                                ;
                            }
                        },
                        optionValue: function (value) {
                            var
                                escapedValue = module.escape.value(value),
                                $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
                                hasOption = ($option.length > 0)
                            ;
                            if (hasOption) {
                                return;
                            }
                            // temporarily disconnect observer
                            module.disconnect.selectObserver();
                            if (module.is.single()) {
                                module.verbose('Removing previous user addition');
                                $input.find('option.' + className.addition).remove();
                            }
                            $('<option/>')
                                .prop('value', escapedValue)
                                .addClass(className.addition)
                                .html(value)
                                .appendTo($input)
                            ;
                            module.verbose('Adding user addition as an <option>', value);
                            module.observe.select();
                        },
                        userSuggestion: function (value) {
                            var
                                $addition = $menu.children(selector.addition),
                                $existingItem = module.get.item(value),
                                alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length,
                                hasUserSuggestion = $addition.length > 0,
                                html
                            ;
                            if (settings.useLabels && module.has.maxSelections()) {
                                return;
                            }
                            if (value === '' || alreadyHasValue) {
                                $addition.remove();
                                return;
                            }
                            if (hasUserSuggestion) {
                                $addition
                                    .data(metadata.value, value)
                                    .data(metadata.text, value)
                                    .attr('data-' + metadata.value, value)
                                    .attr('data-' + metadata.text, value)
                                    .removeClass(className.filtered)
                                ;
                                if (!settings.hideAdditions) {
                                    html = settings.templates.addition(module.add.variables(message.addResult, value));
                                    $addition
                                        .html(html)
                                    ;
                                }
                                module.verbose('Replacing user suggestion with new value', $addition);
                            }
                            else {
                                $addition = module.create.userChoice(value);
                                $addition
                                    .prependTo($menu)
                                ;
                                module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
                            }
                            if (!settings.hideAdditions || module.is.allFiltered()) {
                                $addition
                                    .addClass(className.selected)
                                    .siblings()
                                    .removeClass(className.selected)
                                ;
                            }
                            module.refreshItems();
                        },
                        variables: function (message, term) {
                            var
                                hasCount = (message.search('{count}') !== -1),
                                hasMaxCount = (message.search('{maxCount}') !== -1),
                                hasTerm = (message.search('{term}') !== -1),
                                values,
                                count,
                                query
                            ;
                            module.verbose('Adding templated variables to message', message);
                            if (hasCount) {
                                count = module.get.selectionCount();
                                message = message.replace('{count}', count);
                            }
                            if (hasMaxCount) {
                                count = module.get.selectionCount();
                                message = message.replace('{maxCount}', settings.maxSelections);
                            }
                            if (hasTerm) {
                                query = term || module.get.query();
                                message = message.replace('{term}', query);
                            }
                            return message;
                        },
                        value: function (addedValue, addedText, $selectedItem) {
                            var
                                currentValue = module.get.values(),
                                newValue
                            ;
                            if (module.has.value(addedValue)) {
                                module.debug('Value already selected');
                                return;
                            }
                            if (addedValue === '') {
                                module.debug('Cannot select blank values from multiselect');
                                return;
                            }
                            // extend current array
                            if ($.isArray(currentValue)) {
                                newValue = currentValue.concat([addedValue]);
                                newValue = module.get.uniqueArray(newValue);
                            }
                            else {
                                newValue = [addedValue];
                            }
                            // add values
                            if (module.has.selectInput()) {
                                if (module.can.extendSelect()) {
                                    module.debug('Adding value to select', addedValue, newValue, $input);
                                    module.add.optionValue(addedValue);
                                }
                            }
                            else {
                                newValue = newValue.join(settings.delimiter);
                                module.debug('Setting hidden input to delimited value', newValue, $input);
                            }

                            if (settings.fireOnInit === false && module.is.initialLoad()) {
                                module.verbose('Skipping onadd callback on initial load', settings.onAdd);
                            }
                            else {
                                settings.onAdd.call(element, addedValue, addedText, $selectedItem);
                            }
                            module.set.value(newValue, addedValue, addedText, $selectedItem);
                            module.check.maxSelections();
                        },
                    },

                    remove: {
                        active: function () {
                            $module.removeClass(className.active);
                        },
                        activeLabel: function () {
                            $module.find(selector.label).removeClass(className.active);
                        },
                        empty: function () {
                            $module.removeClass(className.empty);
                        },
                        loading: function () {
                            $module.removeClass(className.loading);
                        },
                        initialLoad: function () {
                            initialLoad = false;
                        },
                        upward: function ($currentMenu) {
                            var $element = $currentMenu || $module;
                            $element.removeClass(className.upward);
                        },
                        leftward: function ($currentMenu) {
                            var $element = $currentMenu || $menu;
                            $element.removeClass(className.leftward);
                        },
                        visible: function () {
                            $module.removeClass(className.visible);
                        },
                        activeItem: function () {
                            $item.removeClass(className.active);
                        },
                        filteredItem: function () {
                            if (settings.useLabels && module.has.maxSelections()) {
                                return;
                            }
                            if (settings.useLabels && module.is.multiple()) {
                                $item.not('.' + className.active).removeClass(className.filtered);
                            }
                            else {
                                $item.removeClass(className.filtered);
                            }
                            module.remove.empty();
                        },
                        optionValue: function (value) {
                            var
                                escapedValue = module.escape.value(value),
                                $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
                                hasOption = ($option.length > 0)
                            ;
                            if (!hasOption || !$option.hasClass(className.addition)) {
                                return;
                            }
                            // temporarily disconnect observer
                            if (selectObserver) {
                                selectObserver.disconnect();
                                module.verbose('Temporarily disconnecting mutation observer');
                            }
                            $option.remove();
                            module.verbose('Removing user addition as an <option>', escapedValue);
                            if (selectObserver) {
                                selectObserver.observe($input[0], {
                                    childList: true,
                                    subtree: true
                                });
                            }
                        },
                        message: function () {
                            $menu.children(selector.message).remove();
                        },
                        searchWidth: function () {
                            $search.css('width', '');
                        },
                        searchTerm: function () {
                            module.verbose('Cleared search term');
                            $search.val('');
                            module.set.filtered();
                        },
                        userAddition: function () {
                            $item.filter(selector.addition).remove();
                        },
                        selected: function (value, $selectedItem) {
                            $selectedItem = (settings.allowAdditions)
                                ? $selectedItem || module.get.itemWithAdditions(value)
                                : $selectedItem || module.get.item(value)
                            ;

                            if (!$selectedItem) {
                                return false;
                            }

                            $selectedItem
                                .each(function () {
                                    var
                                        $selected = $(this),
                                        selectedText = module.get.choiceText($selected),
                                        selectedValue = module.get.choiceValue($selected, selectedText)
                                    ;
                                    if (module.is.multiple()) {
                                        if (settings.useLabels) {
                                            module.remove.value(selectedValue, selectedText, $selected);
                                            module.remove.label(selectedValue);
                                        }
                                        else {
                                            module.remove.value(selectedValue, selectedText, $selected);
                                            if (module.get.selectionCount() === 0) {
                                                module.set.placeholderText();
                                            }
                                            else {
                                                module.set.text(module.add.variables(message.count));
                                            }
                                        }
                                    }
                                    else {
                                        module.remove.value(selectedValue, selectedText, $selected);
                                    }
                                    $selected
                                        .removeClass(className.filtered)
                                        .removeClass(className.active)
                                    ;
                                    if (settings.useLabels) {
                                        $selected.removeClass(className.selected);
                                    }
                                })
                            ;
                        },
                        selectedItem: function () {
                            $item.removeClass(className.selected);
                        },
                        value: function (removedValue, removedText, $removedItem) {
                            var
                                values = module.get.values(),
                                newValue
                            ;
                            if (module.has.selectInput()) {
                                module.verbose('Input is <select> removing selected option', removedValue);
                                newValue = module.remove.arrayValue(removedValue, values);
                                module.remove.optionValue(removedValue);
                            }
                            else {
                                module.verbose('Removing from delimited values', removedValue);
                                newValue = module.remove.arrayValue(removedValue, values);
                                newValue = newValue.join(settings.delimiter);
                            }
                            if (settings.fireOnInit === false && module.is.initialLoad()) {
                                module.verbose('No callback on initial load', settings.onRemove);
                            }
                            else {
                                settings.onRemove.call(element, removedValue, removedText, $removedItem);
                            }
                            module.set.value(newValue, removedText, $removedItem);
                            module.check.maxSelections();
                        },
                        arrayValue: function (removedValue, values) {
                            if (!$.isArray(values)) {
                                values = [values];
                            }
                            values = $.grep(values, function (value) {
                                return (removedValue != value);
                            });
                            module.verbose('Removed value from delimited string', removedValue, values);
                            return values;
                        },
                        label: function (value, shouldAnimate) {
                            var
                                $labels = $module.find(selector.label),
                                $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(value) + '"]')
                            ;
                            module.verbose('Removing label', $removedLabel);
                            $removedLabel.remove();
                        },
                        activeLabels: function ($activeLabels) {
                            $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
                            module.verbose('Removing active label selections', $activeLabels);
                            module.remove.labels($activeLabels);
                        },
                        labels: function ($labels) {
                            $labels = $labels || $module.find(selector.label);
                            module.verbose('Removing labels', $labels);
                            $labels
                                .each(function () {
                                    var
                                        $label = $(this),
                                        value = $label.data(metadata.value),
                                        stringValue = (value !== undefined)
                                            ? String(value)
                                            : value,
                                        isUserValue = module.is.userValue(stringValue)
                                    ;
                                    if (settings.onLabelRemove.call($label, value) === false) {
                                        module.debug('Label remove callback cancelled removal');
                                        return;
                                    }
                                    module.remove.message();
                                    if (isUserValue) {
                                        module.remove.value(stringValue);
                                        module.remove.label(stringValue);
                                    }
                                    else {
                                        // selected will also remove label
                                        module.remove.selected(stringValue);
                                    }
                                })
                            ;
                        },
                        tabbable: function () {
                            if (module.is.searchSelection()) {
                                module.debug('Searchable dropdown initialized');
                                $search
                                    .removeAttr('tabindex')
                                ;
                                $menu
                                    .removeAttr('tabindex')
                                ;
                            }
                            else {
                                module.debug('Simple selection dropdown initialized');
                                $module
                                    .removeAttr('tabindex')
                                ;
                                $menu
                                    .removeAttr('tabindex')
                                ;
                            }
                        },
                        clearable: function () {
                            $icon.removeClass(className.clear);
                        }
                    },

                    has: {
                        menuSearch: function () {
                            return (module.has.search() && $search.closest($menu).length > 0);
                        },
                        search: function () {
                            return ($search.length > 0);
                        },
                        sizer: function () {
                            return ($sizer.length > 0);
                        },
                        selectInput: function () {
                            return ($input.is('select'));
                        },
                        minCharacters: function (searchTerm) {
                            if (settings.minCharacters) {
                                searchTerm = (searchTerm !== undefined)
                                    ? String(searchTerm)
                                    : String(module.get.query())
                                ;
                                return (searchTerm.length >= settings.minCharacters);
                            }
                            return true;
                        },
                        firstLetter: function ($item, letter) {
                            var
                                text,
                                firstLetter
                            ;
                            if (!$item || $item.length === 0 || typeof letter !== 'string') {
                                return false;
                            }
                            text = module.get.choiceText($item, false);
                            letter = letter.toLowerCase();
                            firstLetter = String(text).charAt(0).toLowerCase();
                            return (letter == firstLetter);
                        },
                        input: function () {
                            return ($input.length > 0);
                        },
                        items: function () {
                            return ($item.length > 0);
                        },
                        menu: function () {
                            return ($menu.length > 0);
                        },
                        message: function () {
                            return ($menu.children(selector.message).length !== 0);
                        },
                        label: function (value) {
                            var
                                escapedValue = module.escape.value(value),
                                $labels = $module.find(selector.label)
                            ;
                            if (settings.ignoreCase) {
                                escapedValue = escapedValue.toLowerCase();
                            }
                            return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) + '"]').length > 0);
                        },
                        maxSelections: function () {
                            return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
                        },
                        allResultsFiltered: function () {
                            var
                                $normalResults = $item.not(selector.addition)
                            ;
                            return ($normalResults.filter(selector.unselectable).length === $normalResults.length);
                        },
                        userSuggestion: function () {
                            return ($menu.children(selector.addition).length > 0);
                        },
                        query: function () {
                            return (module.get.query() !== '');
                        },
                        value: function (value) {
                            return (settings.ignoreCase)
                                ? module.has.valueIgnoringCase(value)
                                : module.has.valueMatchingCase(value)
                                ;
                        },
                        valueMatchingCase: function (value) {
                            var
                                values = module.get.values(),
                                hasValue = $.isArray(values)
                                    ? values && ($.inArray(value, values) !== -1)
                                    : (values == value)
                            ;
                            return (hasValue)
                                ? true
                                : false
                                ;
                        },
                        valueIgnoringCase: function (value) {
                            var
                                values = module.get.values(),
                                hasValue = false
                            ;
                            if (!$.isArray(values)) {
                                values = [values];
                            }
                            $.each(values, function (index, existingValue) {
                                if (String(value).toLowerCase() == String(existingValue).toLowerCase()) {
                                    hasValue = true;
                                    return false;
                                }
                            });
                            return hasValue;
                        }
                    },

                    is: {
                        active: function () {
                            return $module.hasClass(className.active);
                        },
                        animatingInward: function () {
                            return $menu.transition('is inward');
                        },
                        animatingOutward: function () {
                            return $menu.transition('is outward');
                        },
                        bubbledLabelClick: function (event) {
                            return $(event.target).is('select, input') && $module.closest('label').length > 0;
                        },
                        bubbledIconClick: function (event) {
                            return $(event.target).closest($icon).length > 0;
                        },
                        alreadySetup: function () {
                            return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0);
                        },
                        animating: function ($subMenu) {
                            return ($subMenu)
                                ? $subMenu.transition && $subMenu.transition('is animating')
                                : $menu.transition && $menu.transition('is animating')
                                ;
                        },
                        leftward: function ($subMenu) {
                            var $selectedMenu = $subMenu || $menu;
                            return $selectedMenu.hasClass(className.leftward);
                        },
                        disabled: function () {
                            return $module.hasClass(className.disabled);
                        },
                        focused: function () {
                            return (document.activeElement === $module[0]);
                        },
                        focusedOnSearch: function () {
                            return (document.activeElement === $search[0]);
                        },
                        allFiltered: function () {
                            return ((module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered());
                        },
                        hidden: function ($subMenu) {
                            return !module.is.visible($subMenu);
                        },
                        initialLoad: function () {
                            return initialLoad;
                        },
                        inObject: function (needle, object) {
                            var
                                found = false
                            ;
                            $.each(object, function (index, property) {
                                if (property == needle) {
                                    found = true;
                                    return true;
                                }
                            });
                            return found;
                        },
                        multiple: function () {
                            return $module.hasClass(className.multiple);
                        },
                        remote: function () {
                            return settings.apiSettings && module.can.useAPI();
                        },
                        single: function () {
                            return !module.is.multiple();
                        },
                        selectMutation: function (mutations) {
                            var
                                selectChanged = false
                            ;
                            $.each(mutations, function (index, mutation) {
                                if (mutation.target && $(mutation.target).is('select')) {
                                    selectChanged = true;
                                    return true;
                                }
                            });
                            return selectChanged;
                        },
                        search: function () {
                            return $module.hasClass(className.search);
                        },
                        searchSelection: function () {
                            return (module.has.search() && $search.parent(selector.dropdown).length === 1);
                        },
                        selection: function () {
                            return $module.hasClass(className.selection);
                        },
                        userValue: function (value) {
                            return ($.inArray(value, module.get.userValues()) !== -1);
                        },
                        upward: function ($menu) {
                            var $element = $menu || $module;
                            return $element.hasClass(className.upward);
                        },
                        visible: function ($subMenu) {
                            return ($subMenu)
                                ? $subMenu.hasClass(className.visible)
                                : $menu.hasClass(className.visible)
                                ;
                        },
                        verticallyScrollableContext: function () {
                            var
                                overflowY = ($context.get(0) !== window)
                                    ? $context.css('overflow-y')
                                    : false
                            ;
                            return (overflowY == 'auto' || overflowY == 'scroll');
                        },
                        horizontallyScrollableContext: function () {
                            var
                                overflowX = ($context.get(0) !== window)
                                    ? $context.css('overflow-X')
                                    : false
                            ;
                            return (overflowX == 'auto' || overflowX == 'scroll');
                        }
                    },

                    can: {
                        activate: function ($item) {
                            if (settings.useLabels) {
                                return true;
                            }
                            if (!module.has.maxSelections()) {
                                return true;
                            }
                            if (module.has.maxSelections() && $item.hasClass(className.active)) {
                                return true;
                            }
                            return false;
                        },
                        openDownward: function ($subMenu) {
                            var
                                $currentMenu = $subMenu || $menu,
                                canOpenDownward = true,
                                onScreen = {},
                                calculations
                            ;
                            $currentMenu
                                .addClass(className.loading)
                            ;
                            calculations = {
                                context: {
                                    offset: ($context.get(0) === window)
                                        ? {top: 0, left: 0}
                                        : $context.offset(),
                                    scrollTop: $context.scrollTop(),
                                    height: $context.outerHeight()
                                },
                                menu: {
                                    offset: $currentMenu.offset(),
                                    height: $currentMenu.outerHeight()
                                }
                            };
                            if (module.is.verticallyScrollableContext()) {
                                calculations.menu.offset.top += calculations.context.scrollTop;
                            }
                            onScreen = {
                                above: (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height,
                                below: (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height
                            };
                            if (onScreen.below) {
                                module.verbose('Dropdown can fit in context downward', onScreen);
                                canOpenDownward = true;
                            }
                            else if (!onScreen.below && !onScreen.above) {
                                module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
                                canOpenDownward = true;
                            }
                            else {
                                module.verbose('Dropdown cannot fit below, opening upward', onScreen);
                                canOpenDownward = false;
                            }
                            $currentMenu.removeClass(className.loading);
                            return canOpenDownward;
                        },
                        openRightward: function ($subMenu) {
                            var
                                $currentMenu = $subMenu || $menu,
                                canOpenRightward = true,
                                isOffscreenRight = false,
                                calculations
                            ;
                            $currentMenu
                                .addClass(className.loading)
                            ;
                            calculations = {
                                context: {
                                    offset: ($context.get(0) === window)
                                        ? {top: 0, left: 0}
                                        : $context.offset(),
                                    scrollLeft: $context.scrollLeft(),
                                    width: $context.outerWidth()
                                },
                                menu: {
                                    offset: $currentMenu.offset(),
                                    width: $currentMenu.outerWidth()
                                }
                            };
                            if (module.is.horizontallyScrollableContext()) {
                                calculations.menu.offset.left += calculations.context.scrollLeft;
                            }
                            isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width);
                            if (isOffscreenRight) {
                                module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight);
                                canOpenRightward = false;
                            }
                            $currentMenu.removeClass(className.loading);
                            return canOpenRightward;
                        },
                        click: function () {
                            return (hasTouch || settings.on == 'click');
                        },
                        extendSelect: function () {
                            return settings.allowAdditions || settings.apiSettings;
                        },
                        show: function () {
                            return !module.is.disabled() && (module.has.items() || module.has.message());
                        },
                        useAPI: function () {
                            return $.fn.api !== undefined;
                        }
                    },

                    animate: {
                        show: function (callback, $subMenu) {
                            var
                                $currentMenu = $subMenu || $menu,
                                start = ($subMenu)
                                    ? function () {
                                    }
                                    : function () {
                                        module.hideSubMenus();
                                        module.hideOthers();
                                        module.set.active();
                                    },
                                transition
                            ;
                            callback = $.isFunction(callback)
                                ? callback
                                : function () {
                                }
                            ;
                            module.verbose('Doing menu show animation', $currentMenu);
                            module.set.direction($subMenu);
                            transition = module.get.transition($subMenu);
                            if (module.is.selection()) {
                                module.set.scrollPosition(module.get.selectedItem(), true);
                            }
                            if (module.is.hidden($currentMenu) || module.is.animating($currentMenu)) {
                                if (transition == 'none') {
                                    start();
                                    $currentMenu.transition('show');
                                    callback.call(element);
                                }
                                else if ($.fn.transition !== undefined && $module.transition('is supported')) {
                                    $currentMenu
                                        .transition({
                                            animation: transition + ' in',
                                            debug: settings.debug,
                                            verbose: settings.verbose,
                                            duration: settings.duration,
                                            queue: true,
                                            onStart: start,
                                            onComplete: function () {
                                                callback.call(element);
                                            }
                                        })
                                    ;
                                }
                                else {
                                    module.error(error.noTransition, transition);
                                }
                            }
                        },
                        hide: function (callback, $subMenu) {
                            var
                                $currentMenu = $subMenu || $menu,
                                duration = ($subMenu)
                                    ? (settings.duration * 0.9)
                                    : settings.duration,
                                start = ($subMenu)
                                    ? function () {
                                    }
                                    : function () {
                                        if (module.can.click()) {
                                            module.unbind.intent();
                                        }
                                        module.remove.active();
                                    },
                                transition = module.get.transition($subMenu)
                            ;
                            callback = $.isFunction(callback)
                                ? callback
                                : function () {
                                }
                            ;
                            if (module.is.visible($currentMenu) || module.is.animating($currentMenu)) {
                                module.verbose('Doing menu hide animation', $currentMenu);

                                if (transition == 'none') {
                                    start();
                                    $currentMenu.transition('hide');
                                    callback.call(element);
                                }
                                else if ($.fn.transition !== undefined && $module.transition('is supported')) {
                                    $currentMenu
                                        .transition({
                                            animation: transition + ' out',
                                            duration: settings.duration,
                                            debug: settings.debug,
                                            verbose: settings.verbose,
                                            queue: false,
                                            onStart: start,
                                            onComplete: function () {
                                                callback.call(element);
                                            }
                                        })
                                    ;
                                }
                                else {
                                    module.error(error.transition);
                                }
                            }
                        }
                    },

                    hideAndClear: function () {
                        module.remove.searchTerm();
                        if (module.has.maxSelections()) {
                            return;
                        }
                        if (module.has.search()) {
                            module.hide(function () {
                                module.remove.filteredItem();
                            });
                        }
                        else {
                            module.hide();
                        }
                    },

                    delay: {
                        show: function () {
                            module.verbose('Delaying show event to ensure user intent');
                            clearTimeout(module.timer);
                            module.timer = setTimeout(module.show, settings.delay.show);
                        },
                        hide: function () {
                            module.verbose('Delaying hide event to ensure user intent');
                            clearTimeout(module.timer);
                            module.timer = setTimeout(module.hide, settings.delay.hide);
                        }
                    },

                    escape: {
                        value: function (value) {
                            var
                                multipleValues = $.isArray(value),
                                stringValue = (typeof value === 'string'),
                                isUnparsable = (!stringValue && !multipleValues),
                                hasQuotes = (stringValue && value.search(regExp.quote) !== -1),
                                values = []
                            ;
                            if (isUnparsable || !hasQuotes) {
                                return value;
                            }
                            module.debug('Encoding quote values for use in select', value);
                            if (multipleValues) {
                                $.each(value, function (index, value) {
                                    values.push(value.replace(regExp.quote, '&quot;'));
                                });
                                return values;
                            }
                            return value.replace(regExp.quote, '&quot;');
                        },
                        string: function (text) {
                            text = String(text);
                            return text.replace(regExp.escape, '\\$&');
                        }
                    },

                    setting: function (name, value) {
                        module.debug('Changing setting', name, value);
                        if ($.isPlainObject(name)) {
                            $.extend(true, settings, name);
                        }
                        else if (value !== undefined) {
                            if ($.isPlainObject(settings[name])) {
                                $.extend(true, settings[name], value);
                            }
                            else {
                                settings[name] = value;
                            }
                        }
                        else {
                            return settings[name];
                        }
                    },
                    internal: function (name, value) {
                        if ($.isPlainObject(name)) {
                            $.extend(true, module, name);
                        }
                        else if (value !== undefined) {
                            module[name] = value;
                        }
                        else {
                            return module[name];
                        }
                    },
                    debug: function () {
                        if (!settings.silent && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.debug.apply(console, arguments);
                            }
                        }
                    },
                    verbose: function () {
                        if (!settings.silent && settings.verbose && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.verbose.apply(console, arguments);
                            }
                        }
                    },
                    error: function () {
                        if (!settings.silent) {
                            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
                            module.error.apply(console, arguments);
                        }
                    },
                    performance: {
                        log: function (message) {
                            var
                                currentTime,
                                executionTime,
                                previousTime
                            ;
                            if (settings.performance) {
                                currentTime = new Date().getTime();
                                previousTime = time || currentTime;
                                executionTime = currentTime - previousTime;
                                time = currentTime;
                                performance.push({
                                    'Name': message[0],
                                    'Arguments': [].slice.call(message, 1) || '',
                                    'Element': element,
                                    'Execution Time': executionTime
                                });
                            }
                            clearTimeout(module.performance.timer);
                            module.performance.timer = setTimeout(module.performance.display, 500);
                        },
                        display: function () {
                            var
                                title = settings.name + ':',
                                totalTime = 0
                            ;
                            time = false;
                            clearTimeout(module.performance.timer);
                            $.each(performance, function (index, data) {
                                totalTime += data['Execution Time'];
                            });
                            title += ' ' + totalTime + 'ms';
                            if (moduleSelector) {
                                title += ' \'' + moduleSelector + '\'';
                            }
                            if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
                                console.groupCollapsed(title);
                                if (console.table) {
                                    console.table(performance);
                                }
                                else {
                                    $.each(performance, function (index, data) {
                                        console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
                                    });
                                }
                                console.groupEnd();
                            }
                            performance = [];
                        }
                    },
                    invoke: function (query, passedArguments, context) {
                        var
                            object = instance,
                            maxDepth,
                            found,
                            response
                        ;
                        passedArguments = passedArguments || queryArguments;
                        context = element || context;
                        if (typeof query == 'string' && object !== undefined) {
                            query = query.split(/[\. ]/);
                            maxDepth = query.length - 1;
                            $.each(query, function (depth, value) {
                                var camelCaseValue = (depth != maxDepth)
                                    ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                                    : query
                                ;
                                if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) {
                                    object = object[camelCaseValue];
                                }
                                else if (object[camelCaseValue] !== undefined) {
                                    found = object[camelCaseValue];
                                    return false;
                                }
                                else if ($.isPlainObject(object[value]) && (depth != maxDepth)) {
                                    object = object[value];
                                }
                                else if (object[value] !== undefined) {
                                    found = object[value];
                                    return false;
                                }
                                else {
                                    module.error(error.method, query);
                                    return false;
                                }
                            });
                        }
                        if ($.isFunction(found)) {
                            response = found.apply(context, passedArguments);
                        }
                        else if (found !== undefined) {
                            response = found;
                        }
                        if ($.isArray(returnedValue)) {
                            returnedValue.push(response);
                        }
                        else if (returnedValue !== undefined) {
                            returnedValue = [returnedValue, response];
                        }
                        else if (response !== undefined) {
                            returnedValue = response;
                        }
                        return found;
                    }
                };

                if (methodInvoked) {
                    if (instance === undefined) {
                        module.initialize();
                    }
                    module.invoke(query);
                }
                else {
                    if (instance !== undefined) {
                        instance.invoke('destroy');
                    }
                    module.initialize();
                }
            })
        ;
        return (returnedValue !== undefined)
            ? returnedValue
            : $allModules
            ;
    };

    $.fn.dropdown.settings = {

        silent: false,
        debug: false,
        verbose: false,
        performance: true,

        on: 'click',    // what event should show menu action on item selection
        action: 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})

        values: false,      // specify values to use for dropdown

        clearable: false,      // whether the value of the dropdown can be cleared

        apiSettings: false,
        selectOnKeydown: true,       // Whether selection should occur automatically when keyboard shortcuts used
        minCharacters: 0,          // Minimum characters required to trigger API call

        filterRemoteData: false,      // Whether API results should be filtered after being returned for query term
        saveRemoteData: true,       // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh

        throttle: 200,        // How long to wait after last user input to search remotely

        context: window,     // Context to use when determining if on screen
        direction: 'auto',     // Whether dropdown should always open in one direction
        keepOnScreen: true,       // Whether dropdown should check whether it is on screen before showing

        match: 'both',     // what to match against with search selection (both, text, or label)
        fullTextSearch: false,      // search anywhere in value (set to 'exact' to require exact matches)

        placeholder: 'auto',     // whether to convert blank <select> values to placeholder text
        preserveHTML: true,       // preserve html when selecting value
        sortSelect: false,      // sort selection on init

        forceSelection: true,       // force a choice on blur with search selection

        allowAdditions: false,      // whether multiple select should allow user added values
        ignoreCase: false,       // whether to consider values not matching in case to be the same
        hideAdditions: true,       // whether or not to hide special message prompting a user they can enter a value

        maxSelections: false,      // When set to a number limits the number of selections to this count
        useLabels: true,       // whether multiple select should filter currently active selections from choices
        delimiter: ',',        // when multiselect uses normal <input> the values will be delimited with this character

        showOnFocus: true,       // show menu on focus
        allowReselection: false,      // whether current value should trigger callbacks when reselected
        allowTab: true,       // add tabindex to element
        allowCategorySelection: false,      // allow elements with sub-menus to be selected

        fireOnInit: false,      // Whether callbacks should fire when initializing dropdown values

        transition: 'auto',     // auto transition will slide down or up based on direction
        duration: 200,        // duration of transition

        glyphWidth: 1.037,      // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width

        // label settings on multi-select
        label: {
            transition: 'scale',
            duration: 200,
            variation: false
        },

        // delay before event
        delay: {
            hide: 300,
            show: 200,
            search: 20,
            touch: 50
        },

        /* Callbacks */
        onChange: function (value, text, $selected) {
        },
        onAdd: function (value, text, $selected) {
        },
        onRemove: function (value, text, $selected) {
        },

        onLabelSelect: function ($selectedLabels) {
        },
        onLabelCreate: function (value, text) {
            return $(this);
        },
        onLabelRemove: function (value) {
            return true;
        },
        onNoResults: function (searchTerm) {
            return true;
        },
        onShow: function () {
        },
        onHide: function () {
        },

        /* Component */
        name: 'Dropdown',
        namespace: 'dropdown',

        message: {
            addResult: 'Add <b>{term}</b>',
            count: '{count} selected',
            maxSelections: 'Max {maxCount} selections',
            noResults: 'No results found.',
            serverError: 'There was an error contacting the server'
        },

        error: {
            action: 'You called a dropdown action that was not defined',
            alreadySetup: 'Once a select has been initialized behaviors must be called on the created ui dropdown',
            labels: 'Allowing user additions currently requires the use of labels.',
            missingMultiple: '<select> requires multiple property to be set to correctly preserve multiple values',
            method: 'The method you called is not defined.',
            noAPI: 'The API module is required to load resources remotely',
            noStorage: 'Saving remote data requires session storage',
            noTransition: 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
        },

        regExp: {
            escape: /[-[\]{}()*+?.,\\^$|#\s]/g,
            quote: /"/g
        },

        metadata: {
            defaultText: 'defaultText',
            defaultValue: 'defaultValue',
            placeholderText: 'placeholder',
            text: 'text',
            value: 'value'
        },

        // property names for remote query
        fields: {
            remoteValues: 'results',  // grouping for api results
            values: 'values',   // grouping for all dropdown values
            disabled: 'disabled', // whether value should be disabled
            name: 'name',     // displayed dropdown text
            value: 'value',    // actual dropdown value
            text: 'text'      // displayed text when selected
        },

        keys: {
            backspace: 8,
            delimiter: 188, // comma
            deleteKey: 46,
            enter: 13,
            escape: 27,
            pageUp: 33,
            pageDown: 34,
            leftArrow: 37,
            upArrow: 38,
            rightArrow: 39,
            downArrow: 40
        },

        selector: {
            addition: '.addition',
            dropdown: '.ui.dropdown',
            hidden: '.hidden',
            icon: '> .dropdown.icon',
            input: '> input[type="hidden"], > select',
            item: '.item',
            label: '> .label',
            remove: '> .label > .delete.icon',
            siblingLabel: '.label',
            menu: '.menu',
            message: '.message',
            menuIcon: '.dropdown.icon',
            search: 'input.search, .menu > .search > input, .menu input.search',
            sizer: '> input.sizer',
            text: '> .text:not(.icon)',
            unselectable: '.disabled, .filtered'
        },

        className: {
            active: 'active',
            addition: 'addition',
            animating: 'animating',
            clear: 'clear',
            disabled: 'disabled',
            empty: 'empty',
            dropdown: 'ui dropdown',
            filtered: 'filtered',
            hidden: 'hidden transition',
            item: 'item',
            label: 'ui label',
            loading: 'loading',
            menu: 'menu',
            message: 'message',
            multiple: 'multiple',
            placeholder: 'default',
            sizer: 'sizer',
            search: 'search',
            selected: 'selected',
            selection: 'selection',
            upward: 'upward',
            leftward: 'left',
            visible: 'visible'
        }

    };

    /* Templates */
    $.fn.dropdown.settings.templates = {

        // generates dropdown from select values
        dropdown: function (select) {
            var
                placeholder = select.placeholder || false,
                values = select.values || {},
                html = ''
            ;
            html += '<i class="dropdown icon"></i>';
            if (select.placeholder) {
                html += '<div class="default text">' + placeholder + '</div>';
            }
            else {
                html += '<div class="text"></div>';
            }
            html += '<div class="menu">';
            $.each(select.values, function (index, option) {
                html += (option.disabled)
                    ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
                    : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
                ;
            });
            html += '</div>';
            return html;
        },

        // generates just menu from select
        menu: function (response, fields) {
            var
                values = response[fields.values] || {},
                html = ''
            ;
            $.each(values, function (index, option) {
                var
                    maybeText = (option[fields.text])
                        ? 'data-text="' + option[fields.text] + '"'
                        : '',
                    maybeDisabled = (option[fields.disabled])
                        ? 'disabled '
                        : ''
                ;
                html += '<div class="' + maybeDisabled + 'item" data-value="' + option[fields.value] + '"' + maybeText + '>';
                html += option[fields.name];
                html += '</div>';
            });
            return html;
        },

        // generates label for multiselect
        label: function (value, text) {
            return text + '<i class="delete icon"></i>';
        },


        // generates messages like "No results"
        message: function (message) {
            return message;
        },

        // generates user addition to selection menu
        addition: function (choice) {
            return choice;
        }

    };

})(jQuery, window, document);
